目录
volatile的作用
- 保证变量在多线程中的可见性
- 保证指令的有序性(禁止指令重排)
保证变量在多线程中的可见性
-
主存中有一个变量a, 它被volatile关键字所修饰, 并且它的值是0
-
T1和T2线程读取了变量a, 此时它们的线程的本地内存(CPU寄存器或高速缓存)中的值都是0
-
如果T1修改了变量a的值为1, 由于被a变量被volatile所修饰, 那么T1修改之后的a就会被刷新到主存(写屏障), 并且执行T2线程的CPU对a变量的缓存行会失效(读屏障), 强制从主存中获取值, 到达了多线程中变量的可见性的目的
-
通过内存屏障保证变量在多线程中的可见性, 如果字段被volatile修饰:
- 读屏障: 在读指令前插入读屏障,可以让高速缓存中的数据失效,重新从主内存加载数据
- 写屏障: 在写指令之后插入写屏障,能让写入缓存的最新数据写回到主内存
保证指令的有序性(禁止指令重排)
指令重排的意义
-
JVM能够根据CPU的特性(CPU的多级缓存系统、多核处理器等)适当的重新排序机器指令,使机器指令更符合CPU的执行特点,最大限度的发挥CPU的性能,提高效率。
-
单线程情况下, 指令重排没问题, 但是多线程环境下, 指令重排可能造成程序的执行结果并不是我们所期待的, 比如单例模式的DCL写法对DCL单例模式的思考
volatile通过两点保证指令有序性
happens-before
happens-before原则(定义一些禁止编译优化的场景,保证并发编程的正确性), 下图来源于: 来源《Java并发编程实践》
内存屏障
-
编译器在生成字节码时, 会在指令序列中插入内存屏障,会多出一个 lock 前缀指令
-
内存屏障是一组处理器指令,解决禁止指令重排序和内存可见性的问题,保证指令重排序后与之前的输出结果一 样,使性能得到优化, 处理器在进行重排序时是会考虑指令之间的数据依赖性。
-
先于这个内存屏障的指令必须先执行,后于这个内存屏障 的指令必须后执行
使用 volatile 的经典场景
- 双重校验锁 DCL(double checked locking)
- ConcurrentHashMap的哈希数组Node[]
- 原子类例如AtomicInteger的value属性
- 多个线程操作同一块内存的同一个变量(保证有序性和内存可见性,有序性和可见性同等重要,都不能忽略)