00:00
接下来给大家介绍V的最后一个特性,指令禁重排,也就是保证我们程序的有序性,通过禁止这样的一种手段来保证程序的有序,那通过前面的讲解我们清楚,对于我们的源代码到最后机器码执行中间呢,可能会经过操作系统,编译器,内存的不同优化和排序,也即我们所写的顺序和最终编译器执行的顺序可能效果会有一些出入。那为了避免这样的一种。因为重排序导致程序结果出现意外的情况,所以有时候我们需要通过来进行指令进重排,好,那么同学们来看一眼。我们所谓的重排序是指。编译器和处理器为了优化程序性能而对指令序列进行重新排列的一种手段,那么这个呢?就有一个要点要把握住。是否存在数据依赖关系来决定是否可以重拍,那么我不管你怎么折腾。
01:00
你重拍以后的指令,你重拍是为了更好的。发挥出编译器内存系统的效果,让我们程序运行的最佳。但最终你不能改变原有的创新语义,这一点的设计必须重点考虑,那么按照重排序的分类和执行流程。从源代码,比如说我们写的hello word.java到最后最终执行的指令序列,那么它是会经过编译器、指令集、内存、系统的方方面面的重排,那么在这块的话。那么可能我们的源代码到最后执行的顺序可能真真正正翻译成字节码以后,和我们编写的顺序可能会有一定的出入。没关系,因为编译器、指令和内存也是来帮我们优化,但是你不管怎么优化,绝对不能改变原有的创新语义,所以说我们要按照数据依赖性来决定是否可以重排。那么什么叫数据依赖性呢?如果说两个操作访问同一个变量,这两个操作当中有一个为写的操作,那么是先写。
02:04
后读还是先读后写?类似于这样,那么这个时候两个操作之间就会存在着数据依赖性。来,同学们请看案例。假如不存在数据依赖性,你随便重排,这种重排序我们是支持,甚至是OK,甚至是无伤大雅,无关紧要的。你编译指令内存系统爱怎么玩怎么玩,没有改变原有的创新语义,那么大家来看一下我们在重排前的时候。大家呢,请看int a等于一,这是第一句int b等于20,第三一个int c等于A加B,那么大家都清楚,如果按照这个语义一加20。没问题吧,那么假设编译器啊给他。重排。AB顺序上下颠倒,那么下面变成B等于20 a等于一这样的一种重排,我相信各位同学们第三句没有任何问题,因为一加20和20加一效果是一样的,类似于我们前面强调过一加二加三等于三加二加一,所以这样的重排没有任何问题。
03:12
假设编译器调整了语句的顺序,但并没有影响。程序的最终运行效果是和我们所编写程序源代码的初衷是一致的,那么你这样的重排是OK的,并且是受欢迎的。但是。如果上下层面存在数据依赖性,那么要禁止重排,因为重排的发生可能会导致程序运行结果和我们的编写初衷不一样。那么来。那么同学们,编译器和处理器在重排序的时候,它会严格的遵守数据依赖性,不会改变存在依赖关系两个操作的执行顺序,但不同的处理器和不同的线程之间,数据器呢,不会被编译器和处理器考虑,由于我们的系统,由于我们的代码对吧,甚至是各个Java的版本,那么其只会作用于单处理器和单线程环境。那么下面。
04:02
这三种情况也是重点,只要重排序的两个操作的执行顺序,可能我们执行效果就会发生改变。那么来同学们请看第一种。俗称写后读。A等于一,B等于A,那么大家它的要求是写一个变量之后再读这个位置,我们现在想达到这样一个效果,但是如果你重排了先是B等于A,那谁知道A上面。是个什么样的效果下来,A等于一,我们只能确定最后一句,所以说这个B的值到底是E还是A的之前的那个值不一定。再来第二种情况叫写后写,那么也记A等于一,A等于二,那么你在读取A的时候。先是一,后是二。上下对调顺序先是二,后是一,那么这个时候你读取的可能是一,可能是二,所以说如果是你写一个变量之后再写这个变量,这样写后写,那么你也要小心。
05:03
第三种叫读后写,A等于B。B等于一,读一个变量之后再写这个变量,那么大家请看B等于一,假设对调啊,然后是A等于B,那么A就是一,但是现在A等于B。那么这个B之前是什么值啊,这个A就会依赖于这个B他们上下之间的顺序,那么这三种情况我们在执行的操作上要特别的小心。好,那接下来我们来。第二次复习我们的底层实现呢,它是通过内存屏障来实现的,那最终完成这个的复习,我们最后用。Code编码的一个小案例,来给大家进一步的说明外的底层原理,好,那么大家继续二,我们大家呢,有关的指令重拍的行为,前面给大家做过这张图。那么就是一横一竖一个圈。再来当第一个操作写的时候,无论第二个操作是什么都不可以重排。那么也就是说,假设第一个操作是读。
06:04
那么第二个操作不管是什么不能重排蓝色这个框框也记这个操作保证Y读之后的所有操作不会重排到Y读之前,那么也就是Y的所谓的外读,就说你要先保证我立刻先从主内存在中。读到最新的值,我读到一个最新的版本,算是有一个交代了,你后等我读完了,你后面再怎么干,OK,那么第二种,当第二个操作写的时候。无论第一个是什么操作都不要重排,那么第二个操作是外斜,那么这个操作保证了外写之前的操作不会重拍,外写之后那么一句话写是要刷新回内存的,那么此时就以我这个版本是最新的了,所以说你先等我写完了,后面呢咱们再说,那么最后。如果第一个操作是写,第二个操作是读,那么这个时候也不能重排,也就是什么写。
07:02
How。读OK,有点类似于我们买色,你先commit,确实增删改这种写操作,确认了我卡了以后我们再去读,那么这样是不是可以避免单独啊,其实效果和道理是相通的。所以。那么结合他们,我们的四大屏障插入的情况就是它。来,每一个better的写操作前面会插入一个store store屏障,每一个写操作的后面会插入一个storelo的屏障,那么其效果第一个store是可以保证在我来跳写之前,其前面的所有普通写操作都刷新回内存了。漏,那么作用是避免跳写与后面可能有的读写的操作重排序,那么自然前面是写,另外一半就是发读读,那么漏漏。Load store,那么来load load用来禁止处理器把上面的读和下面的普通读进行重排,那么load store,那么也是禁止处理器把上面的边读,你下面的普通写进行重排,好,你别甭着急,那么同学们,我们通过两次的加强说明,我相信如果到这儿你还是懵逼的话是正常的,那接下来我们快快的过了这些理论知识,放心一定会有案例给大家说明白,来看一下我们的案例。
08:30
来,同学们。很简单的一个小DEMO int I等于零,它是一个普通的变量,这有个修饰的布尔型的变量,默认值是false,那按照我们的想法和顺序,一个线程先要让I等于零变为I等于二,然后再把flag设置为错,如果flag是错的话,那这个时候我们打出来的值I就是二。So easy,但是请大家看,根据前面所讲。
09:00
一个是数字型的I,一个是布尔型的flag,它们两个在赋值的过程当中可没有数据依赖性。那么也即假设我们产生了。指令重排,也就是说顺序不再是先I后flag的设置顺序,而是变成flag放在第一行,I等于二放在第二行,对调一下,那么这个时候就会变成如果说别人来读的时候这个flag。If等于处马上就打出来这个I,那么这个时候这个I有可能是初始值零,而不再是我们希望赋值为二以后打出的最新的效果。所以如果在这块产生指令重排,那么可能我们的程序的原始语义,我们编写该程序的程序员的作者的本意会被破坏掉,那么所以说同学们,我们这儿需要加入这个V来保证只禁止指令重拍,保证程序的一贯和语义。那么下面请看一下。
10:03
来。为了保证同学们呢,看的清楚啊,我们先这么干。请大家看。Int I是个普通写int I对吧,它是个普通,只有布尔型的这个flag它才是个vela的,那么按照我们前面所学的理论,现在我们来落地。在每一个V的写操作前面,每个V的写操作,那么这个flag等于错,就是这一行,它写操作的前面要加一个屏障叫store store,那么他的意思就说静止上面的普通写就是I等于二和下面的外写就是二的flag等于出。产生重排序就不会出现,也就是我们要求我们的顺序就是一定要先I等于二,然后再flag等于错,而不要搞得上下对调以后flag先弄I等于二,结果这块如果是错了,这个I的值还没负完呢,有可能会读到零,那么这样我们程序的语义是不是就挂了?那么下面再来看。
11:02
在每每个的写操作后面就是个flag等一处这块插入一个多的屏障。也就是什么这。Flag等于错,前面会插入一个store store后面会插入一个,那么以插入这个以后就是禁止上面的这个写和下面可能有的Y读或者Y写来进行重排序,也即我们的顺序就是要I等于二,然后在flag等于to完了以后,如果后面有代码,我们呢在顺序执行,不允许他们错位,那么这样就可以保证我们的指令重排达到最终的效果,就是完了他要写。之前的操作都禁止重拍到来泰尔特。外的之后,那么顺序给你固定死啊,那么这样程序的语义只有一个唯一的路径,是不是得到了一种保证呢?OK,所以说在外斜之前会插入store,防止上下重排V来跳斜之后会插入,那么禁止上面的V斜和下面产生勾搭,那么保证顺序就这样,OK,好,那么这个是我们的外写,那么接下来请看在这一块呢。
12:12
程序呢,还是我们这小块啊,我们呢依旧给大家拿过来,那么现在if。Flag是true还是false,这个就是一个读了,那么好在每一个的读操作后面会插入一个load load这么一个屏障。那么if flag是读,后面插入这个屏障,那么禁止处理器把上面的读和下面的普通读重排序,OK,那么不允许,我们只要有了外写,说明我们的主内存已经是最新的值了,你必须先给我拿到最新的,还记得我们前面验证过那个是吗?可见性吧,只要这个flag。被改过了,又是未来要修饰的,马上要去主内存。读到最新的,那么这样的话就是先等我读完了以后,我确认了,咱们再来说后面的操作,好第二个在每一个来读操作后面再插入一个load store,就是load load。
13:09
然后load store。禁止处理器把上面的读和下面的普通写重排序,那么这样是不是保证了一定是外读完了以后,咱们再进行后面的普通读的操作,语义的正确和一致性通过这些来得到了加强和巩固啊,诶那么这个呢,就是我们的什么禁止指令重排为了他它最重要的。特性之一。
我来说两句