Volatile关键字的作用
- 作用
- 可见性
- CPU层面的高速缓存
- 指令重排序
- 编译器层面的优化
- 补充
作用
- 可以保证在多线程环境下共享变量的可见性。
- 通过增加内存屏障防止多个指令之间的重排序。
可见性
是指当一个线程对于共享变量的修改,其他线程可以立刻看到修改之后的一个值。这个可见性问题,本质上是由多方面造成的。
CPU层面的高速缓存
在CPU里面设计了三级缓存去解决CPU运算效率和内存IO效率的问题。但是这也带来了缓存一致性的问题。而在多线程并行执行的情况下,缓存一致性问题就会导致可见性问题。对于增加了volatiile关键字的修饰的一个共享变量,JVM虚拟机会自动去增加一个#lock汇编指令。而这个指令会去根据不同的CPU型号去自动添加总线锁或者缓存锁。
【补充】:
总线锁:他锁定的是CPU的前端总线,从而去导致在同一个时刻只能有一个线程和内存通信。这样就避免了多线程并发造成的可见性问题。
缓存锁:缓存锁是对总线锁的一个优化,因为总线锁导致CPU的使用效率大幅度下降。所以缓存锁只针对CPU三级缓存中的目标数据去加锁。而缓存锁是使用MESI缓存一致性协议来实现的。
指令重排序
指令在编写的数据和执行顺序是不一致的。从而在多线程环境下导致可见性问题。指令重排序本质上是一种性能优化的手段。它来自于几个方面:首先第一个方面是CPU层面,针对于MESI协议的更进一步的优化去提升CPU的一个利用率,所以他引入一个叫StoreBuffer的一个机制。这个优化机制会导致CPU的乱序执行。
当然,为了避免这样的问题,CPU提供了内存屏障指令。上层应用可以在合适的地方去插入内存屏障,去避免CPU指令重排序的一个问题。
编译器层面的优化
编译器在编译的过程中,在不改变单线程语义和程序正确性的前提下,对指令进行合理的重排序,从而去优化整体的一个性能。所以,对于共享变量增加了volatile关键字,那么编译器层面就不会去触发编译器的优化。同时在JVM里面,他会插入内存屏障指令来去避免重排序的问题。
补充
当然除了volatile关键字以外,从JDK1.5开始JVM就使用了一种Happens-Before的模型去描述多线程之间的可见性的一个关系,也就是说如果两个操作之间具备Happens-Before关系那么意味着这两个操作具备可见性的一个关系。不需要再额外去考虑增加volatile关键字来提供可见性的一个保障。
参考资料:【Java面试】被面试官问:volatile关键字有什么用?