volatile怎么通过内存屏障保证可见性和有序性?

https://blog.csdn.net/chenzengnian123/article/details/122406807

volatile通过内存屏障保证可见性

小陈:老王,你上一篇抛出一个问题volatile怎么通过内存屏障保证可见性和有序性?我现在迫不及待的想知道了。

老王:嗯嗯,我们慢慢来讲,先说说volatile怎么通过内存屏障来保证可见性?

小陈:volatile关键字实际上是怎么使用内存屏障的呢?

老王:是这样子的。

volatile修饰的变量,在每个读操作(load操作)之前都加上Load屏障,强制从主内存读取最新的数据。每次在assign赋值后面,加上Store屏障,强制将数据刷新到主内存。

老王:以volatile int = 0;线程A、B进行 i++ 的操作来画图给你讲解一下:

如上图所示:

(1)线程A读取 i 的值遇到Load屏障,需要强制从主存读取得到 i = 0; 然后传递给工作线程执行++操作

(2)cpu执行 i++ 操作得到 i = 1,执行assign指令进行赋值;然后遇到Store屏障,需要强制刷新回主内存,此时得到主内存 i = 1

(3)然后线程B执行读取 i 遇到Load屏障强制从主内存读取,得到最新的值 i = 1,然后传给工作线程执行++操作,得到 i = 2,同样在赋值后遇到Store屏障立即将数据刷新回主内存

老王:通过上面的图和讲解,以及volatile读取前加的Load屏障、赋值后加的Store屏障看懂了吗?

小陈:哦哦,通过这样说我就明白了。

其实说白了就是通过一个屏障让volatile的变量每次读都读主存,每次修改后立即刷到主存里面。

好比线程A修改 i 立即将值刷到主存里面,后面线程B用到的时候强制从主存读取,这个时候它能看到的值线程A修改之后的值了。也就是通过这种方式来保证多线程之间的可见性吧。

老王:嘿嘿,没错;就是这个意思......

小陈:volatile通过内存屏障每次走主存的方式;这样来保障可见性,我理解了,害~,感觉也不难嘛......

老王:哈哈,这个本来就不难,只是你需要先了解一下内存屏障,以及这些屏障的作用是什么。再加上之前讲过多核CPU高速缓存JAVA内存模型,你理解起来就很容易了。如果没有之前的知识做铺垫,你理解起来就费劲了......

小陈:哈哈,好像也是啊......

老王:好了,下面我们继续来讨论一下volatile怎么通过内存屏障来保证有序性?

volatile通过内存屏障保证有序性

老王:小陈啊,之前讲过一个有序性问题导致异常的例子,你还记得不?

小陈:记得啊,我记得当时是这样说的:

线程A的执行代码:

 
  1. // 步骤1

  2. dataSource = initDataSource();

  3. // 步骤2

  4. httpClient = initHttpClient();

  5. // 步骤3

  6. initOK = true;

线程B的执行代码:

 
  1. // 步骤4

  2. while(!initOK) {

  3. }

  4. // 步骤5

  5. Object data = dataSource.getData();

  6. // 步骤6

  7. httpClient.request(data);

由于线程A先执行了initOK = true。导致线程B提前跳出了while循环!!!,然后线程B调用dataSource.getData的时候发现dataSource没初始化好,竟然是个坑爹的null,导致代码报错了

老王:哈哈,看来你很用功啊;之前的例子你都记得。

老王:现在我们就来讲讲将initOk用volatile来修饰,是可以做到线程A有序性执行的。

好了,废话不多说,我先来上代码:

 
  1. // 步骤1

  2. dataSource = initDataSource();

  3. // 步骤2

  4. httpClient = initHttpClient();

  5. // 步骤3

  6. initOK = true;

对应到指令可能是这样的:

 
  1. // 步骤1 对应上面dataSource = initDataSource();

  2. store datasource指令

  3. // 步骤2 对应上面httpClient = initHttpClient();

  4. store http指令

  5. StoreStore屏障 (注意:在store initOK前面加了一个StoreStore屏障)

  6. // 步骤3 对应上面initOK = true;

  7. store initOk = true指令

  8. StoreLoad 屏障 (注意:在store initOK后面加了一个StoreLoad屏障)

注意这里:store initOk指令的前面加了一道StoreStore屏障后面加了一道StoreLoad屏障

所以通过volatile修饰initOK加了屏障之后store initOK = true 这一条指令是不能跳到store dataSource、store http前面去的,所以必须先执行完前面的执行之后,才能执行store initOK = true

这样对于线程B来说,加了内存屏障之后,它看到线程A就是资源初始化完成之后,才将initOK表示设置为true的,这样它看到线程A的执行就是有序

老王:小陈,我这么说你懂了不?

小陈:稍等,我来捋捋思路....

也就是通过加了屏障store initOK = true 指令不能跟前面的store指令进行交换。所以它就自然得等前面的store指令执行完了之后才执行store initOK = true的对吧? 然后在线程B那一侧看到的initOK = true的时候,发现资源以及初始化好了,自然就不会报错了。

老王:bingo,就是这个道理....

小陈:这个volatile写的时候前面加StoreStore屏障、写的后面加StoreLoad屏障来禁止重排序的我看懂了。当volatile读的时候加什么屏障来禁止重排序?

老王:这个就当作思考题,你自己再去看看咯,原理也是一样的......

小陈:好的老王,在线程安全来说volatile保证了可见性、有序性了;我看过一些资料说volatile是不能保证原子性的,那它为啥不能保证原子性啊?

老王:今天我们先讲到这里,你先消化消化。我们下一章再来讨论volatile不能保证原子性的问题......

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值