Java 多线程--volatile详解

一、volatile简述

volatile是JMM的一种具体实现方式,是一种轻量级的synchronized,用于修饰共享变量,在多线程环境下能够保证原子性可见性有序性

二、volatile如何解决可见性问题

volatile修饰的变量如何保证在其被修改之后,能够被其他线程立马得到修改之后的值?

缓存一致性协议

当CPU写数据的时候发现这个变量是共享变量的时候,会发出信号通知(总线嗅探)其他CPU将该变量的缓存设置成无效状态,因此当其他CPU需要读取的时候,发现当前变量的缓存行被设置成无效了,自然会到主内存中读取最新的变量值。(那么其他CPU如何发现数据是否无效呢?)

总线嗅探

每个处理器通过嗅探在总线上传播的数据来检查自己的缓存是不是过期了,如果过期了,则会将自己的处理器缓存行设置成无效状态,等下次处理器去读取缓存行的时候会发现缓存过期,自然去主内存中读取最新的数据。

总线风暴

由于volatile的缓存一致性,需要其他CPU不断地从主内存嗅探,无效的交互会导致总线带宽达到峰值,因此不要大量使用volatile。

happens-before 语义

可以发现,如果让我们去从底层去理解内存可见性,那么其实很难理解整个程序的可见性了,因此从JDK 5开始,通过引入happends-before这个概念来介绍操作之间内存可见性的问题:

一个线程中的每个操作,都是happens-before于线程中的后续操作。

volatile域规则:对一个volatile域的写操作,happens-before于任意线程后续对改变量的读。

三、volatile如何禁止指令重排序

为什么会重排序?

因为在程序执行时,为了提高性能,编译器和处理器常常会对既定的代码执行顺序进行指令的重排序,达到最优的执行性能。

内存屏障

Java编译器会在生成指令系列时的适当位置中加入内存屏障,来阻止处理器的重排序。

Java 多线程--volatile详解

as-if-serial 语义

volatile其实是遵守as-if-serial语义,也就是说不管怎么重排序,单线程程序所执行的结果是不会改变的。如果操作之间存在着依赖关系,这时候是不会重排序的,但是如果不存在依赖关系,那么编译器和执行器就可以进行指令重排序,但是无论怎么重排序,它跟单线程程序所执行的结果都是一样。

四、volatile不能解决原子性问题

主要不能解决i++的问题,这样的原子性问题,一是可以使用加锁的形式,二是可以使用原子类。

五、用volatile实现单例模式

详情请看单例模式章节

七、总结

  • volatile属于轻量级synchronized,读和写都是无锁的,能保证可见性和有序性,但并不能保证原子性,无法取代synchronized。

  • volatile只能修饰于变量。

  • volatile提供了happens-before保证,对volatile变量的写happens-before所有其他线程后续对该变量的读操作。

  • volatile可以在单例双重检查中实现可见性和禁止指令重排序,从而保证安全性。

  • volatile适用于做触发器的场景,某个属性被多个线程共享,其中一个线程修改了变量值之后,其他线程能够立马得到值,比如boolean flag做触发器使用

八、参考资料

https://mp.weixin.qq.com/s/Oa3tcfAFO9IgsbE22C5TEg

https://juejin.im/post/6844903865343541261#heading-5

https://blog.****.net/u010571316/article/details/64906481