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

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

        Java并发包下的类中大量使用了volatile关键字。经过以前文章介绍,你们已经知道了volatile的三大特性:共享变量可见性;不保证原子性;禁止指令重排后顺序性。经过前面两篇文章咱们经过代码验证了前两个特性,本文咱们就来验证禁止指令重排保证顺序性。程序员

        指令重排序的生活例子

        去餐厅吃饭预约位置的的时候。假设要去A餐厅吃饭,A餐厅有前台B、服务员C以及老板D。若是就只有你一我的去吃饭的时候,你给前台或者给服务器或者给老板说一声把2号桌预约了,半小时后过来。餐厅在为了2小时内就你一我的去吃饭。那么OK,没问题,别说等半个小时,就是等一个小时,2号桌仍是你的。编程

        可是,若是如今是吃饭高峰期,不少人来吃饭,你给前台说了,前台忙着没有及时给服务员或者没有给老板说,这个时候有个路人甲来吃饭,恰好看到2号桌没人,老板或者服务员就让他就坐2号桌吃饭了。那么,等你过来的时候,2号桌已经有人了。这个时候对于你来讲,这个结果就不是你想要的了。缓存

        上面案例,若是从计算机执行指令角度来分析的话,你要到2号桌吃饭,这是预期结果。餐厅A就至关因而处理器,前台B就至关因而编译器,服务员C和老板D就是指令和内存系统。若是你预约的时间点不是吃饭高峰期或者没有人去餐厅A吃饭。那么你就至关因而一个线程。就是单线程的。老板、前台、服务员怎么安排均可以。由于只有你一个2号桌确定是你的。这是单线程状况下。预期结果与实际结果就是一致的。安全

        若是你预约的时间点是吃饭高峰期,不少人来吃饭(不少线程),这个时候为了餐厅效益,不管是前台仍是服务员或者是老板都会对你的位置进行重排序。在你没有来的时候,会安排其余人到你预约的位置吃饭。若是其余人在你的位置吃饭,这个时候你再来吃饭,那么实际结果和预期结果就不同了。这个时候餐厅应该作出相应的赔偿。为了解决这种赔偿问题,老板就想到了一个方案。作个牌子放在客人预约的桌子上。服务器

        当前台或者是服务员或者是老板看到餐桌上放的这个牌子,就知道这个位置不能再调动了。其中这个放在餐桌上的牌子就是特殊类型的内存屏障了。多线程

        示意图以下:并发

        0fJheB4W1TM

        

        再来举个更常见的例子:ide

        考试,在考试的时候老师会告诉咱们,先作会作的,不会作的放到后面作。假设出题老师出题顺序是1-5,可是考试会根据本身实际状况作题顺序有多是一、二、四、五、3或者是一、三、四、五、2等等。若是把出题老师看着是写代码的程序员,题目的顺序是代码一行一行的顺序,你的老师会告诉你先作会作的,此时老师就至关因而编译器,会排序一次。而后你本身作的时候又会进行从新排序,你本身就至关因而处理器又排序了一次。性能

        上面两个现实生活中的案例,咱们弄明白后,再来看看在计算机中指令重排问题,就很容易理解了。

        指令重排

        咱们程序员编写的代码在JVM执行的时候,为了提升性能,编译器和处理器都会对代码编译后的指令进行重排序。分为3种:

        1:编译器优化重排:

        编译器的优化前提是在保证不改变单线程语义的状况下,对从新安排语句的执行顺序。

        2:指令并行重排:

        若是代码中某些语句之间不存在数据依赖,处理器能够改变语句对应机器指令的顺序

        如:int x = 10;int y = 5;对于这种x y之间没有数据依赖关系的,机器指令就会进行从新排序。可是对于:int x = 10; int y = 5; int z = x+y;这种的,由于z和x y之间存在数据依赖(z=x+y)关系。在这种状况下,机器指令就不会把z排序在xy前面。

        3:内存系统的重排序

        经过以前的学习,咱们知道了处理器和主内存之间还存在一二三级缓存。这些读写缓存的存在,使得程序的加载和存取操做,多是乱序无章的。

        指令重排序的流程图

        经过上面介绍,咱们能够知道从程序员写的Java源码处处理器真正实际执行的指令序列,会经历以下图的过程:

        0fJheBRpS2S

        

        执行顺序:

        源码编译器优化重排序(第一次排序) 指令重排序(第二次)内存重排序(第三次) 最终指向的指令。

        不管是第一次编译器的重排序仍是第2、三次的处理器重排序。这些重排序当在多线程的场景下可能会出现线程可见性的问题。

        如在多线程的状况下,单例模式就不安全了。

        为了解决这个问题,JMM容许编译器在生成指令顺序的时候,能够插入特定类型的内存屏障来禁止指令重排序。

        当一个变量使用volatile修饰的时候,volatile关键字就是内存屏障。当编译器在生成指令顺序的时候,发现了volatile,就直接忽略掉。再也不重排序了。

        示意图:

        0fJheC69UkC

        

        证实volatile禁止指令重排演示代码,欢迎继续学习下一篇文章

        欢迎关注凯哥公众号:凯哥Java(kaigejava)

e8369572a8d3666d7c0437b83367a212.jpg

相关文章
相关标签/搜索