volatile原理解析——JUC多线程与高并发系列笔记

volatile原理解析——JUC多线程与高并发系列笔记

1、volatile特性

保证可见性

禁止指令重排序【与sychronized的重要区别】

不保证原子性【与sychronized的重要区别】

2、什么是主内存与工作内存

每个线程私有自己的工作内存,线程间的交互通过主内存实现
volatile原理解析——JUC多线程与高并发系列笔记
主内存是共享内存

当A线程修改了成员变量的值,并刷新到主内存中,如果变量是不可见的。不管是主线程还是其他线程都不会重新到主内存中拿到A线程修改后的值,只有当变量被volatile修改后,才会通知其他线程去获取新的值。

volatile不保证原子性,因为加了volatile的变量仍然会出现值覆盖的问题,例如在处理i++的时候,由于i++不是原子操作,在jvm中它是分为三步来处理的,执行GETFIELD拿到主内存中的原始值i,执行IADD进行加1操作,执行PUTFIELD把工作内存中的值写回主内存中。如果是多线程环境下,当多个线程同时对主内存中的值加了1后,将新的值都刷入了主内存中,此时就将丢失了正确的值。

处理原子性问题,可以使用atomic等原子类来解决、

3、指令重排序

源代码 -> 编译器的优化重排 -> 指令并行的重排 -> 内存系统的重排 -> 最终执行的指令

单线程里确保程序最终执行的结果与代码顺序执行的结果一致

处理器在进行重排序时必须要考虑指令之间的数据依赖

多线程环境下线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致是无法确定的,结果无法预测

代码展示:
volatile原理解析——JUC多线程与高并发系列笔记
解析:在多线程环境下,由于线程交替执行,同时由于可能产生指令重排序,语句1和语句2可能会交替顺序,导致语句3执行得出的值为5。当然更多的情况应该是等于6

volatile是禁止指令重排序的,因为它在指令间插入了内存屏障(memory barrier),它会告诉cpu和编译器,不管什么指令都不能和这条内存屏障重排序,也就是说内存屏障禁止在内存屏障前后的指令执行重排序。

内存屏障的另一个作用,就是强制刷出各种cpu中缓存的数据,因此任何cpu上的线程都能读到这些数据的最新版本

深入volatile执行重排序之双端检索单例模式

代码展示
volatile原理解析——JUC多线程与高并发系列笔记
在我们单例模式将要创建对象的时候,jvm其实指令其实是有三个操作指令

1、memory=allocate(); // 分配内存空间

2、instance(memory); // 初始化

3、instance = memory; // 设置instance指向刚刚分配的内存地址,此时instance != null

此时有可能会出现问题[涉及jvm重排,控制不了],由于cpu和编译器会优化执行指令重排序,导致以上初始化和分配内存地址这两步出现指令重排。导致还未初始化,就分配了内存地址,在多线程的环境下,此时它已经判断当前引用是不为空的,因此直接会返回引用,但是这个引用只包含内存地址,但是此地址还没有值,所以此线程拿到的是空。

< END >
欢迎读者关注,技术路上共同前行

volatile原理解析——JUC多线程与高并发系列笔记