聊聊我对 Volatile的理解
说之前,需要先了解下计算模型
CPU运算速度很快,内存的运算速度与CPU 元算速度相差很多倍,cpu在计算时,需要从速度与之相差不多的Cache取数据 寄存器>L1>L2>L3 (速度对比) ,CPU在操作数据时,数据从内存读到L3->L2->L1->寄存器 ,cpu处理完数据 从寄存器->L1->L2->L3->内存
JMM模型(Java Memory Model简称JMM)
jmm只是一种根据计算机模型抽象出的一种规范,是围绕原子性,可见性,有序性展开的,这个规范控制程序变量在共享区域,私有区域的访问方式,我认为,这个规范是Java可以在不同平台运行的根本,“一次编译,多处运行”
CPU操作数据流程
下面每一个操作都是具有原子性的
- read: 把变量值 a 从主内存传输到线程的工作内存中
- load: 把read操作从主内存中得到的变量值放入工作内存的变量副本中
- use: 把工作内存中的一个变量值传递给线程1
- assign:把操作后的值赋值给工作内存中的变量
- store:把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作
- write:把store操作变量,刷新到主内存
问题:线程1和线程n,同时对变量a做写操作,就会出现并发问题,
解决方式:
① 对主内存中的变量a进行加锁(总线加锁)
② volatile修饰变量a
总线加锁:对主内存中的变量a 加锁,其他线程要使用到a,需要等到线程1操作完,释放对象锁. 线程②才可以去拿到变量a,去做操作
缺点:锁力度太大,效率低
volatile:在store过程lock 刷新到内存unlock
优点:锁力度小,效率高
围绕volatile可见性,谈谈我的理解
一写多读的情况
首先看段代码
public class VolatileVisibilityTest {
private boolean flag = true;
public void refresh() {
this.flag = false;
String name = Thread.currentThread().getName();
System.out.println("线程"+name + "修改了共享变量initFlag");
}
public void load() {
String name = Thread.currentThread().getName();
while (flag) {
}
System.out.println(name + "当前线程感知到initFlag改变");
}
public static void main(String[] args) throws InterruptedException {
VolatileVisibilityTest visibilityTest = new VolatileVisibilityTest();
Thread a = new Thread(() -> {
visibilityTest.refresh();
}, "线程a");
//线程B 做空循环,线程A修改共享变量flag 值
Thread b = new Thread(() -> {
visibilityTest.load();
}, "线程b");
b.start();
Thread.sleep(200);
a.start();
}
}
图解如下
多写的情况如图:
我有个疑问:如果同一时间,两个线程都去修改变量a为M状态,哪个线程说了算???
一个指令周期内,会对指令进行裁决,所以不可能同时出现两个线程都修改为M,具体硬件是如何裁决的,这就要去关注cpu 怎么裁决的,我就不太晓得了
如果线程n裁决胜利,线程1是否会立刻从内存中去读取?
裁决失败(实际是就是把数据的缓存行置为失效)
不一定,取决与这段程序指令,是否要求再去读变量a,比如线程线程1要做两次循环,比如第一次裁决失败,还会再次去读取
缓存行(Cache line)是什么?有多大?
缓存行就是cpu 缓存的最小存储单元,可能有32字节 ,64字节 128字节,不同厂家cpu 缓存行大小不一 ,类似于机械硬盘最小存储单元是 簇 是一个道理
什么情况下MESI协议会失效???
如果变量存储长度超过一个缓存行,MESI协议就会失效,会加总线锁(对主内存变量加锁)
围绕Volatile的有序性谈谈我的理解
先看下图:
图中的红色过程都是原子操作,一定是顺序执行,但是不保证连续执行
先说结论:被volatile修饰后 read load use 操作 和assign store write 操作必须是连续的 也就是说read load use 这三个原子操作被捆绑到一起了 必须一起执行,assign store write 这三个操作也是同样的道理
为什么volatile可以保证有序性呢???
答:内存屏障可以保证不会发生指令重排
我理解的内存屏障?
内存屏障是cpu 提供的一些指令集,实现对内存操作的顺序限制
有下面几种类型
- LoadLoad屏障(读读):对于这样的语句Load1; LoadLoad; Load2,在Load2读取前,Load1必须读取完成
- StoreStore屏障(写写): 对于这样的语句Store1; StoreStore; Store2,在Store2写数据之前,Store1的写操作必须对Store2操作可见
- LoadStore屏障(读写): 对于这样的语句Load1; LoadStore; Store2, 在Store2写操作被刷到主内存之前,必须等Load1读取数据已完成
- StoreLoad屏障(写读): 对于这样的语句Store1; StoreLoad; Load2, 在Load2读取之前,必须保证Store1写入操作对Load2可见
问题:为什么Volatile对于一写多读是安全的?多写情况下不能保证线程安全?
答:Volatile 不能保证原子性,导致在多写情况下会出现线程不安全问题
围绕Volatile不能保证原子性,谈谈我的理解
看图:
上面说到过 ,lock,read,load,use,assign,store,write,unlock,这个八个都是原子操作
被Volatile修饰后.可以保证read,load,use操作和assign,store,write为原子操作,
但是不能保证原子操作A和B连续执行
如图,
如果Volatile和CAS滥用会带来什么问题?
总线风暴
如果你有不同见解,请在评论区留言交流吧