[超级链接:Java并发学习系列-绪论]
在Java并发编程中,如果要保证代码的安全性,则必须保证代码的原子性、可见性和有序性。
在 Java并发12:并发三特性-原子性、可见性和有序性概述及问题示例中,对并发中的三个特性(原子性、可见性和有序性)进行了初步学习。
本章主要就Java中保障可见性的技术进行更加全面的学习。
场景说明:
定义普通的共享变量:
//普通情况下,多线程不能保证可见性
private static boolean stop;
下面的代码展示了在多线程环境中,对共享变量的共享:
//普通情况下,多线程不能保证可见性
new Thread(() -> {
System.out.println("Ordinary A is running...");
while (!stop) ;
System.out.println("Ordinary A is terminated.");
}).start();
Thread.sleep(10);
new Thread(() -> {
System.out.println("Ordinary B is running...");
stop = true;
System.out.println("Ordinary B is terminated.");
}).start();
某次运行结果:
Ordinary A is running...
Ordinary B is running...
Ordinary B is terminated.
通过观察结果,发现程序确实存在可见性问题。
在Java中提供了多种可见性保障措施,这里主要涉及四种:
使用volatile关键字修饰共享变量stop:
//使用volatile能够保证可见性
private volatile static boolean vStop;
在多线程环境中,对volatile修饰的共享变量stop进行共享测试:
//通过volatile关键字保证可见性
new Thread(() -> {
System.out.println("Volatile A is running...");
while (!vStop) ;
System.out.println("Volatile A is terminated.");
}).start();
new Thread(() -> {
System.out.println("Volatile B is running...");
vStop = true;
System.out.println("Volatile B is terminated.");
}).start();
运行结果(多次):
Volatile A is running...
Volatile B is running...
Volatile B is terminated.
Volatile A is terminated.
通过多次运行,发现运行结果一致,所以可以确定volatile关键字能够保证代码的可见性。
定义一个共享对象用于synchronized关键字进行同步加锁:
//通过synchronized同步代码块保证可见性
private static byte[] obj = new byte[0];
在多线程环境中进行对obj进行加锁、等待和唤醒:
//通过synchronized同步代码块保证可见性
new Thread(() -> {
System.out.println("Synchronized A is running...");
synchronized (obj) {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
while (!stop) ;
}
System.out.println("Synchronized A is terminated.");
}).start();
Thread.sleep(10);
new Thread(() -> {
System.out.println("Synchronized B is running...");
synchronized (obj) {
stop = true;
obj.notify();
}
System.out.println("Synchronized B is terminated.");
}).start();
运行结果(多次):
Synchronized A is running...
Synchronized B is running...
Synchronized B is terminated.
Synchronized A is terminated.
通过多次运行,发现运行结果一致,所以可以确定synchronized关键字能够保证代码的可见性。
定义Lock接口和Condition接口:
//通过Lock接口保证可见性
private static ReentrantLock lock = new ReentrantLock(true);
private static Condition condition = lock.newCondition();
在多线程环境中进行Lock加锁、condition等待和唤醒:
//通过Lock同步代码块保证可见性
new Thread(() -> {
System.out.println("Lock A is running...");
lock.lock();
try {
condition.await();
while (!stop) ;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
System.out.println("Lock A is terminated.");
}).start();
new Thread(() -> {
System.out.println("Lock B is running...");
lock.lock();
try {
stop = true;
condition.signal();
} finally {
lock.unlock();
}
System.out.println("Lock B is terminated.");
}).start();
运行结果(多次):
Lock A is running...
Lock B is running...
Lock B is terminated.
Lock A is terminated.
通过多次运行,发现运行结果一致,所以可以确定Lock接口能够保证代码的可见性。
使用AtomicBoolean定义共享变量stop:
//通过Atomic保证可见性
private static AtomicBoolean aStop = new AtomicBoolean(false);
在多线程环境中,对使用AtomicBoolean定义的共享变量stop进行共享测试:
//通过Atomic保证可见性
new Thread(() -> {
System.out.println("Atomic A is running...");
while (!aStop.get()) ;
System.out.println("Atomic A is terminated.");
}).start();
new Thread(() -> {
System.out.println("Atomic B is running...");
aStop.set(true);
System.out.println("Atomic B is terminated.");
}).start();
运行结果(多次):
Atomic A is running...
Atomic B is running...
Atomic B is terminated.
Atomic A is terminated.
通过多次运行,发现运行结果一致,所以可以确定Atomic类型能够保证代码的可见性。
经验证,以下四种措施,可以保证Java代码在运行时的可见性:
特性 | volatile关键字 | synchronized关键字 | Lock接口 | Atomic变量 |
---|---|---|---|---|
原子性 | 无法保障 | 可以保障 | 可以保障 | 可以保障 |
可见性 | 可以保障 | 可以保障 | 可以保障 | 可以保障 |
有序性 | 一定程度保障 | 可以保障 | 可以保障 | 无法保障 |