凯哥Java■Java并发编程之验证volatile指令重排-理论篇

Java并发编程之验证volatile指令重排-理论篇
Java并发包下的类中大量使用了volatile关键字 。 通过之前文章介绍 , 大家已经知道了volatile的三大特性:共享变量可见性;不保证原子性;禁止指令重排后顺序性 。 通过前面两篇文章我们通过代码验证了前两个特性 , 本文我们就来验证禁止指令重排保证顺序性 。
指令重排序的生活例子
去餐厅吃饭预定位置的的时候 。 假设要去A餐厅吃饭 , A餐厅有前台B、服务员C以及老板D 。 如果就只有你一个人去吃饭的时候 , 你给前台或者给服务器或者给老板说一声把2号桌预定了 , 半小时后过来 。 餐厅在为了2小时内就你一个人去吃饭 。 那么OK , 没问题 , 别说等半个小时 , 就是等一个小时 , 2号桌还是你的 。
【凯哥Java■Java并发编程之验证volatile指令重排-理论篇】但是 , 如果现在是吃饭高峰期 , 很多人来吃饭 , 你给前台说了 , 前台忙着没有及时给服务员或者没有给老板说 , 这个时候有个路人甲来吃饭 , 刚好看到2号桌没人 , 老板或者服务员就让他就坐2号桌吃饭了 。 那么 , 等你过来的时候 , 2号桌已经有人了 。 这个时候对于你来说 , 这个结果就不是你想要的了 。
上面案例 , 如果从计算机执行指令角度来分析的话 , 你要到2号桌吃饭 , 这是预期结果 。 餐厅A就相当于是处理器 , 前台B就相当于是编译器 , 服务员C和老板D就是指令和内存系统 。 如果你预定的时间点不是吃饭高峰期或者没有人去餐厅A吃饭 。 那么你就相当于是一个线程 。 就是单线程的 。 老板、前台、服务员怎么安排都可以 。 因为只有你一个2号桌肯定是你的 。 这是单线程情况下 。 预期结果与实际结果就是一致的 。
如果你预定的时间点是吃饭高峰期 , 很多人来吃饭(很多线程) , 这个时候为了餐厅效益 , 无论是前台还是服务员或者是老板都会对你的位置进行重排序 。 在你没有来的时候 , 会安排其他人到你预定的位置吃饭 。 如果其他人在你的位置吃饭 , 这个时候你再来吃饭 , 那么实际结果和预期结果就不一样了 。 这个时候餐厅应该做出相应的赔偿 。 为了解决这种赔偿问题 , 老板就想到了一个方案 。 做个牌子放在客人预定的桌子上 。
当前台或者是服务员或者是老板看到餐桌上放的这个牌子 , 就知道这个位置不能再调动了 。 其中这个放在餐桌上的牌子就是特殊类型的内存屏障了 。
示意图如下:
凯哥Java■Java并发编程之验证volatile指令重排-理论篇
文章图片
再来举个更常见的例子:
考试 , 在考试的时候老师会告诉我们 , 先做会做的 , 不会做的放到后面做 。 假设出题老师出题顺序是1-5 , 但是考试会根据自己实际情况做题顺序有可能是1、2、4、5、3或者是1、3、4、5、2等等 。 如果把出题老师看着是写代码的程序员 , 题目的顺序是代码一行一行的顺序 , 你的老师会告诉你先做会做的 , 此时老师就相当于是编译器 , 会排序一次 。 然后你自己做的时候又会进行重新排序 , 你自己就相当于是处理器又排序了一次 。
上面两个现实生活中的案例 , 我们弄明白后 , 再来看看在计算机中指令重排问题 , 就很容易理解了 。
指令重排
我们程序员编写的代码在JVM执行的时候 , 为了提高性能 , 编译器和处理器都会对代码编译后的指令进行重排序 。 分为3种:
1:编译器优化重排:
编译器的优化前提是在保证不改变单线程语义的情况下 , 对重新安排语句的执行顺序 。
2:指令并行重排:
如果代码中某些语句之间不存在数据依赖 , 处理器可以改变语句对应机器指令的顺序
如:intx=10;inty=5;对于这种xy之间没有数据依赖关系的 , 机器指令就会进行重新排序 。 但是对于:intx=10;inty=5;intz=x+y;这种的 , 因为z和xy之间存在数据依赖(z=x+y)关系 。 在这种情况下 , 机器指令就不会把z排序在xy前面 。


推荐阅读