深入理解volatile的三大特性----------简单易懂
这篇文章写的不错
https://mp.weixin.qq.com/s/Oa3tcfAFO9IgsbE22C5TEg
1.保证可见性
volatile保证不同线程对共享变量操作的可见性,也就是说一个线程修改了volatile修饰的变量,当修改写回主内存时,另外一个线程立即看到最新的值。
为啥加锁可以解决可见性问题呢?
因为某一个线程进入synchronized代码块前后,线程会获得锁,清空工作内存,从主内存拷贝共享变量最新的值到工作内存成为副本,执行代码,将修改后的副本的值刷新回主内存中,线程释放锁。而获取不到锁的线程会阻塞等待,所以变量的值肯定一直都是最新的。
Volatile做了什么?
每个线程操作数据的时候会把数据从主内存读取到自己的工作内存,如果他操作了数据并且写回了,其他已经读取的线程的变量副本就会失效了,其他线程需要操作数据又要再次去主内存中读取。由于Volatile的缓存一致性协议,需要不断的从主内存嗅探和cas不断循环,无效交互会导致总线带宽达到峰值,所以不要大量使用Volatile 。
2.不保证原子性
在一次操作之内,要么全都成功,要么完全失败,可以通过原子类保证原子性
3.禁止指令重排
那Volatile是怎么保证不会被执行重排序的呢?--------------内存屏障
java编译器会在生成指令系列时在适当的位置会插入内存屏障来禁止特定类型的处理器重排序。
volatile写操作是在前面和后面分别插入内存屏障,而volatile读操作是在后面插入两个内存屏障。
总结
- volatile修饰符适用于以下场景:某个属性被多个线程共享,其中有一个线程修改了此属性,其他线程可以立即得到修改后的值,比如boolean flag;或者作为触发器,实现轻量级同步。
- volatile属性的读写操作都是无锁的,它不能替代synchronized,因为它没有提供原子性和互斥性。因为无锁,不需要花费时间在获取锁和释放锁_上,所以说它是低成本的。
- volatile只能作用于属性,我们用volatile修饰属性,这样compilers就不会对这个属性做指令重排序。
- volatile提供了可见性,任何一个线程对其的修改将立马对其他线程可见,volatile属性不会被线程缓存,始终从主存中读取。
- volatile提供了happens-before保证,对volatile变量v的写入happens-before所有其他线程后续对v的读操作。
- volatile可以使得long和double的赋值是原子的。
- volatile可以在单例双重检查中实现可见性和禁止指令重排序,从而保证安全性。