volatile
volatile是一个java修饰符,它保证所修饰字段的可见性,并且禁止指令重排。
volatile可见性原理
缓存一致性
当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。
每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。
缺点
由于Volatile的MESI缓存一致性协议,需要不断的从主内存嗅探和cas不断循环,无效交互会导致总线带宽达到峰值。
所以不要大量使用Volatile,至于什么时候去使用Volatile什么时候使用锁,根据场景区分。
阻止指令重排
为了提高性能,编译器和处理器常常会对既定的代码执行顺序进行指令重排序。
java编译器会在生成指令系列时在适当的位置会插入内存屏障指令来禁止特定类型的处理器重排序。
为了实现volatile的内存语义,JMM会限制特定类型的编译器和处理器重排序,JMM会针对编译器制定volatile重排序规则表:
happens-before
如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系。
volatile域规则:对一个volatile域的写操作,happens-before于任意线程后续对这个volatile域的读。
如果现在我的变了flag变成了false,那么后面的那个操作,一定要知道我变了。
总结
- volatile修饰符适用于以下场景:某个属性被多个线程共享,其中有一个线程修改了此属性,其他线程可以立即得到修改后的值,比如booleanflag;或者作为触发器,实现轻量级同步。
- volatile属性的读写操作都是无锁的,它不能替代synchronized,因为它没有提供原子性和互斥性。因为无锁,不需要花费时间在获取锁和释放锁_上,所以说它是低成本的。
- volatile只能作用于属性,我们用volatile修饰属性,这样compilers就不会对这个属性做指令重排序。
- volatile提供了可见性,任何一个线程对其的修改将立马对其他线程可见,volatile属性不会被线程缓存,始终从主 存中读取。
- volatile提供了happens-before保证,对volatile变量v的写入happens-before所有其他线程后续对v的读操作。
- volatile可以使得long和double的赋值是原子的。
- volatile可以在单例双重检查中实现可见性和禁止指令重排序,从而保证安全性