volatile
关键字的作用是什么?
相比于 synchronized
关键字(重量级锁)对性能影响较大,Java提供了一种较为轻量级的可见性和有序性问题的解决方案,那就是使用 volatile
关键字。由于使用 volatile
不会引起上下文的切换和调度,所以 volatile
对性能的影响较小,开销较低。
从并发三要素的角度看,volatile
可以保证其修饰的变量的可见性和有序性,无法保证原子性(不能保证完全的原子性,只能保证单次读/写操作具有原子性,即无法保证复合操作的原子性)。
下面将从并发三要素的角度介绍 volatile
如何做到可见和有序的。
1. volatile
如何实现可见性?
什么是可见性?
可见性指当多个线程同时访问共享变量时,一个线程对共享变量的修改,其他线程可以立即看到(即任意线程对共享变量操作时,变量一旦改变所有线程立即可以看到)。
1.1 可见性例子
/**
* volatile 可见性例子
* @author 单程车票
*/
public class VisibilityDemo {
// 构造共享变量
public static boolean flag = true;
// public static volatile boolean flag = true; // 如果使用volatile修饰则可以终止循环
public static void main(String[] args) {
// 线程1更改flag
new Thread(() -> {
// 睡眠3秒确保线程2启动
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {e.printStackTrace();}
// 修改共享变量
flag = false;
System.out.println("修改成功,当前flag为true");
}, "one").start();
// 线程2获取更新后的flag终止循环
new Thread(() -> {
while (flag) {
}
System.out.println("获取到修改后的flag,终止循环");
}, "two").start();
}
}
复制代码
- 不使用
volatile
修饰flag
变量时,运行程序会进入死循环,也就是说线程1对flag
的修改并没有被线程2读到,也就是说这里的flag
并不具备可见性。 - 使用
volatile
修饰flag
变量时,运行程序会终止循环,打印提示语句,说明线程2读到了线程1修改后的数据,也就是说被volatile
修饰的变量具备可见性。
1.2 volatile
如何保证可见性?
被 volatile
修饰的共享变量 flag
被一个线程修改后,JMM(Java内存模型)会把该线程的CPU内存中的共享变量 flag
立即强制刷新回主存中,并且让其他线程的CPU内存中的共享变量 flag
缓存失效,这样当其他线程需要访问该共享变量 flag
时,就会从主存获取最新的数据。
所以通过