阅读本文约“3分钟”


上一次我们说到synchronized互斥代码的实现过程,如果有忘记或不清楚的可以去上篇看看。
【Java猫说】Java多线程之内存可见性(上篇)

今天我们了解下重排序。

其使代码书写的顺序与实现执行的顺序不同,指令重排序是编译器或处理器为了提高程序性能而做的优化,可以分为
1、编译器优化的重排序(编译器优化)
2、指令级并行重排序(处理器优化)
3、内存系统的重排序(处理器优化)

而as-if-serial语义原则是指:无论如何重排序,程序执行的结果应该与代码顺序执行的结果一致(Java编译器、运行时和处理器都会保证Java在单线程下遵循as-if-serial语义)

int num1 = 1;
int num2 = 2;
int sum = num1 + num2;

对于执行的单线程而言,第1、2行的顺序可以重排,但第3行不能
由此,重排序并不会给单线程带来内存可见性问题

但是在多线程中程序交错执行时,重排序可能会造成内存可见性问题

这里罗列了几个原因,导致共享变量在线程间不可见的原因:
1、线程的交叉执行(synchronized原子性)
2、重排序结合线程交叉执行(synchronized原子性)
3、共享变量更新后的值没有在工作内存与主内存间及时更新(synchronized可见性)

而对于另一个对象volatile而言其实现了可见性,但是不能保证原子性(不能保证volatile变量符合操作是的原子性)

深入来说,是通过加入内存屏障和禁止重排序优化来实现的。
1、对volatile变量执行写操作时,会在写操作后加一条store屏障指令
2、对volatile变量执行读操作时,会在读操作前加入一条load屏障指令

由此我们可以分为读写volatile变量的两种操作。

线程写volatile变量的过程:
1、改变线程工作内存中volatile变量副本的值
2、将改变后的副本的值从工作内存刷新到主内存

线程读volatile变量的过程:
1、从主内存中读取volatile变量的最新值到线程的工作内存中
2、从工作内存中读取volatile变量的副本

注意:volatile是不能保证原子性的

想要在多线程中安全的使用volatile变量,必须同时满足一下几个条件:
1、对变量的写入操作不依赖其当前值

- 不满足:number++、count = count * 5
- 满足:boolean变量、记录数据变化的变量等

2、该变量没有包含在具有其他变量的不变式中

- 不满足:不变式 low < up

最后我们来比较下这两个对象吧

- volatile不需要加锁,比synchronized更轻量级,不会阻塞线程
- 从内存可见性角度讲,volatile读相当于加锁,volatile写相当于解锁
- synchronized即能保证可见性,又能保证原子性,而volatile只能保证可见性,无法保证原子性
- volatile没有synchronized使用的广泛



本文已转载个人技术公众号:UncleCatMySelf
欢迎留言讨论与点赞
上一篇推荐:【Java猫说】Java多线程之内存可见性(上篇)
下一篇推荐:【Java猫说】Java对象的行为


Java猫说
1.3k 声望930 粉丝

现架构设计(码农)兼创业技术顾问,不羁平庸,热爱开源,杂谈程序人生与不定期干货。