线程安全的概念与 synchronized
1. 指定加锁对象
指定加锁对象:对给定对象加锁,进入同步代码前要获得给定对象的锁
public class AccountSyncByInstance implements Runnable { public void run() { for (int j = 0; j < 100000; j++) { synchronized (this) { i++; } } } public int getSummary() { return i; } private int i = 0; public static void main(String[] args) throws InterruptedException { AccountSyncByInstance instance = new AccountSyncByInstance(); Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); Thread t3 = new Thread(instance); t1.start(); t2.start(); t3.start(); t1.join(); t2.join(); t3.join(); System.out.println(instance.getSummary()); } }
2. 直接作用于实例方法
相当于对当前实例加锁,进入同步代码前要获得当前实例的锁
public class AccountSyncByMethod implements Runnable { public void run() { for (int j = 0; j < 100000; j++) { synchronized (this) { increase(); } } } private synchronized void increase() { i++; } public int getSummary() { return i; } private int i = 0; public static void main(String[] args) throws InterruptedException { AccountSyncByMethod instance = new AccountSyncByMethod(); Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); Thread t3 = new Thread(instance); t1.start(); t2.start(); t3.start(); t1.join(); t2.join(); t3.join(); System.out.println(instance.getSummary()); } }
3. 直接作用于静态方法
直接作用于静态方法:相当于对当前类加锁,进入同步代码前要获得当前类的锁
一种错误的同步方式如下:
public class AccountSyncByBad implements Runnable { public void run() { for (int j = 0; j < 100000; j++) { synchronized (this) { increase(); } } } private synchronized void increase() { i++; } public int getSummary() { return i; } private static int i = 0; public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new AccountSyncByBad()); Thread t2 = new Thread(new AccountSyncByBad()); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
虽然在第3行的 Increase()方法中,申明这是一个同步方法。
但很不幸的是,执行这段代码的两个线程都指向了不同的 Runnable实例。
Thread t1 = new Thread(new AccountSyncByBad()); Thread t2 = new Thread(new AccountSyncByBad());由第13、14行可以看到,这两个线程的 Runnable实例并不是同一个对象。
因此,线程t1会在进入同步方法前加锁自己的 Runnable实例,而线程t2也关注于自己的对象锁。
换言之,这两个线程使用的是两把不同的锁。因此,线程安全是无法保证的。
但我们只要简单地修改上述代码,就能使其正确执行。
那就是使用 synchronized的第三种用法,将其作用于静态方法
private static synchronized void increase() { i++; }除了用于线程同步、确保线程安全外, synchronized还可以保证线程间的可见性和有序性。