线程安全的概念与 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还可以保证线程间的可见性和有序性。