00:00
各位同学大家好,接下来我们给大家介绍内存屏障,看括号里面这几个字也应该知道本小节一定是重点中的重点,面试中肯定会问,那么接下来想和大家说的是这一章肯定是又硬又难,那么弟兄们不怕跟着杨哥走拿下,那么前面讲过拜拜莱特尔。关键字修饰的变量会有两个特性,第一个可见,及时刷新回主内存,让大家都知道最新的值被改过了,第二个进重排。那么我们前面说过,为了保证程序的高效,编译器啊,内存啊,指令集啊等等都会做一些顺序上的重排,但是有时候我们不希望这个程序自作聪明,主人我自己来搞定。OK,所以说这个时候的话,它这两大特性是非常重要的,那么你有这个诉求,你凭什么可以保证呢?靠的就是内存屏障好。下面先先说这个屏障,屏障从字面意思而言,是不是一种阻塞,一种拦截器一样的,那么说我们生活上的,我们大家都清楚,比如说查酒驾,前面搞了个公路上搞了个路障,如果看到前面的提示啊,那么后面的车辆是不是会控制车流掉头重新走等等等等,那么一样所谓的屏障就是保证了什么,大家走慢点,那么现在。
01:17
有这个屏障,检查一个才能过一个,或者否则你掉头重新走,那么这条公路上是不是得到了一种有序性啊?好,那么下面我们来开工。先来看生活的case啊。那么这个漏如果没有管控的话,顺序是很难保证的,哎,有时候屏障也是起到了一种隔离保护拦截的作用,对吧?哎,不管是水流还是公路上查酒驾,那么来同学们搂一眼。这是某年。黄金周北京长城,杭州西湖,人挤人,请问即便我这儿写着请勿滞留,请靠,又行有效吗?有用吗?没人管,所以说这种东西如果没有管控的话,你的顺序很难保证,西湖掉下去嘛,还能趴了两圈,长城掉下去我估计就没了,对不对?那就是直接不是长城,是缠绵了,OK,好,那么接下来我们来看一下,所以说为了避免这样的无序。
02:13
我们必须要设定规矩,禁止乱序,那么咋整呢?就要加我们的一种拦截,加我们的一种屏障。所以说同学们。看一下上海南京路步行街,武警呢?人墙当红灯走起,我相信看到这样的画面,同学们应该理解什么叫屏障了吧?如果没有这个武警人墙,你紧靠交通灯,那么前面也说过了,不是没提示啊。有用吗?无效的,所以说这种情况下,那么同学们,我们只能是加强管理,加固安全,人墙形成了以后,那么弟兄们也会明白,那么这样在人前形成了以后,是不是在人流之间就形成了一种屏障,那么这样是不是可以阻断部分人流,达到了大家的行走在道路上的安全和有序啊,哎,这就是屏障,他积极的一个一面。
03:04
好,那么所以说我们再说白莱特的两大特性,所以可见。多个线程操作主物理内存的共享变量,第一个先读取到线程自己的本地空间,改完了以后立即刷新回主内存,并及时发出通知啊,各位亲,我已经改完了,现在主内存里面的共享变量已经修改为我最新的版本了,大家可以去主内存拿最新版,前面的修改就是我的这个修改,对后面所有的线程是可见的,好,这个就叫可见性。第二个怎么保证有序,我们说过了,只要有人流,我们有人墙,是不是用这个人墙拦截器,人墙屏障来保证人流有序啊,那么不允许你乱窜,不允许你乱走,那么这个就是什么禁止你。破坏这个交通,不守规矩,所以说有序性我们怎么来看啊,就什么进重排来啥意思呢,前面讲过。
04:01
重排序就是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段,有时候会改变程序语句的先后顺序,但是呢,在重排的基础上,我们前面也说过要有有一个东西叫注意数据的什么依赖性。没有依赖,不存在数据依赖性,你可以重排,只要效果正确,语义不用发生变化,比如一加上二加上三等于三加上二加上一没问题。好,各个不同的硬件和操作系统以及CPU,你有时候在并发的程序下,可能先执行,先加载第二条,比方说int a等于一,Int b等于二,这样的定性A在前,A在。B在前还是A在前,这样的话没有数据依赖,类似的是可以重排,但是一旦存在数据依赖关系,是绝对禁止重排的,那么重排后的指令绝对不能改变原有的创新语义,你重排以后运行的效果给我编的效果不一样了,那么主人是不是白忙活了?所以这一点在并发设计中必须要重点考虑,那么但凡对于某些状态的变化,那么加个V的关键字啊,就是可见加近重排OK,好,那么下面我们有这种考虑了以后就要有程序的手段来实践,那么接下来我们就要告诉大家,你凭什么?
05:22
可以保证可见和有序,进程盘靠的就是内存屏障,怎么啦?所谓内存屏障,也叫内存栅栏,或者叫屏障指令,是一类什么同步屏障指令,那么也就是说就跟那个人武警人墙一样同步,是不是就像是锁定资源一样,这个人墙拉过来了,这个人流就会被阻塞,就会被断档,OK,它是指这种同步指令是CPU或编译器在对内存随机反应的操作中的一个什么同步点,哎,就跟红灯亮起来,无警人墙过去,那么这一段落暂时就不能走了,那么使得词点之前的所有读写操作都执行好。
06:01
才可以开始执行词典之后的操作,那么拦截一下这段时间,这此路不通,另外一段路走,走完了人理以后,我们再打开这个OK,所以说同步也是一种什么拦截阻塞,有点类似于这样啊,一想那个人墙大家应该都明白,我的作用是要避免代码重排序,不允许人流在道路上乱走,所以说用无警源墙隔开,那么所以说内存屏障其实就是一种GPM的指令,Java内存模型重排的规则会要求Java编译器在生成这个指令的时候插入特定的内存屏障,那么这个时候是不是在人流里面插入特定的武警源墙来控制这个人流,起到拦截屏障的作用,反而保证这个交通的有序和安全,那么通过这些内存屏障指令,那么V来就实现了GMM内存模型当中所规定的三大特性中的两个。就是可见性和有序性,那么它这个有序性就是指静止重排,那么但是不拉太没有原子性啊,这儿我们要说清楚啊,GMM内存规范是希望可见有序原子,但是白了太阳三进二只保证两个可见和有序,那么它是怎么保证的呢?
07:12
靠的是底层加内存屏障,那么这个内存屏障是一种CPU的和编译器发出的一种指令,诶这是什么?底层偏汇编硬件级别的操作系统这边底层就带着的,所以说内存屏障之前的所有写操作都要什么回血到主内存,也就是我这加个屏障了。那么屏站之前的这些写操作转到这儿,你别不提交,迅速提到主内存,那么保证后面的读的都是最新的了,那么内存屏障之后所有的读操作都能获得内存屏障之前所有写操作的最新结果集,你看。写操作的最新结果,其实是不是我写完了以后你再去读啊,那么是不是实现了可见性,所以呢。各位亲,那么我们就会明白所谓写平障。
08:02
Memory bar告诉处理器在写屏站什么之前,所有的存储全部。卡到数据同步到主内存,OK,它的作用也就是说当我看到store指令的时候,就必须把该指令之前所有写入指令执行完,就是卡密特到主内存才能继续往下走,那么是不是保证了现在大家有个了断,你遇到我这个写屏站了,那么。在看到这个写屏站,必须把这个指令之前的全部写回进我们的什么主内存。哥们儿别。积累着别攒着了,提交一下OK,好保证数据写回去最新的,那么毒屏站呢,处理器在毒品站之后的读操作。都在读屏障之后执行好,因为前面这个屏障就是保证你先全部写完了,现在主内存里面是最新的了,我再去读,那么这样是不是保证漏的屏障指令之后,就能够保证后面读取到的数据指令一定能够读取到什么最新的,哎,所以说有点类似于一句话就是它。
09:08
哥们赶快全部卡提交到主内存,那么load之后的读到的也就是你前面提交的,那么这样是不是就实现了可见性,哎,我读的就是你提交的最新的劳动成果,那么我现在读到的就是最新的,那么你改的我就马上看到了可见性获得保证好,那么所以说图还是他们三个,那么因此我们在同排序的时候。为了保证这个你不许再指令重排,就是前面的到后面,后面的到前面,看到屏障了,就要规定的动作去执行,该提交的提交,提交完了以后再去读是吧?最新的数据保证可见,所以重排序的时候不允许把内存屏障之后的指令重排续到内存屏障之前,能理解了吗?一句话,对一个变量的写先行发生于任意后续,对这个变量的读也叫写后读,那么我一定是先写完了以后才允许你去读,大家都保证可见性,我读到的是最新最准确的,OK,这个就是内存屏障的意义。
10:11
好,那么下面我们来看一下内存屏障的分类,那么一句话,粗分两种,细分是四种来。我们之前讲过,GMM也说过happen先行发生原则,这种东就是天上飞的理念,是个接口,是个规范,那么你如何落地,落地要靠什么,你凭凭什么听你的,你凭什么保证你管不管用,所以说我得把你什么限制起来,靠的就是内存屏障,那么内存屏障那么大家看。粗分是两种,一个叫读屏障,一个叫写屏障,当然你要是去看书的时候,有些呢还会写有一个负艾瑞叫全屏障,那么其实全屏障也就是这两个的是吗?混合,哎,有点像我们吃火锅,这是白锅,这是辣的红锅,那个全的,那个就是鸳鸯锅,OK,其实本质而言只有这两种啊,读和写,漏和to,那么来所谓读。
11:12
在读指令之前,插入读屏站,让工作内存或CPU高速上的数据就失效,重新回到主内存中获取最新数据,就是只要我碰到毒品站,你就去。主内存里面去读最新的,因为有人已经提交最新的了,你自己本地线程里面的这个私域空间里面这一份,您可以把它理解为就失效了,OK。动过了去读最新的,这个叫读屏站,好那么写屏站呢,在写指令之后插入写屏站,强制就要把写缓冲区的,就是线程里面的那个重新数据刷回到主内存,请立刻做个了断卡密提到一个最新的,好那么同学们这个呢,就是我们的什么读屏站和写屏障它的意义,那接下来那杨哥这个屏障长什么样?
12:01
那么我们就可以立刻给大家呈现,那么为什么说粗分两种,细分是四种呢?两两组合那有四种,那么分别我们来看一眼。来先说CC加底层源码分析,那么我们在idea工具里面先找到Una类的class文件啊,那么下面我们就来看看那么这个屏障长什么样,那么来兄弟们。Unsafe。好,Class。来,弟兄们,我们在403行。大家呢,可以看到load fence,这个叫读屏站,这个呢叫写屏站,For OK,也就是前面的干嘛。白锅红锅,OK,可以近似的理解为就是一种什么混合读写的意思,那么这个是只读,这个是止血,那么这个呢就是混合,那么好,下面再来看那毒屏站和写屏站长什么样,为什么能够加va来就保证UNS类是底层的,所以说我们现在呢要打开CGR的源码。OK,那么来找到了以后。
13:02
我们找找找找找,这有个UNa.Java我们前面强调过UNa.class一定会有个什么UNa.Java那么来,我们打开open gdk这个源码,按shift.java c语言写的,大家请看load store负那是不是这三个,那么这三个的底层涉及到我们的C和汇编,它是如何加载,为什么能够保证你写个未来太阳它就有这个屏障呢?人家出娘胎就自带着了。那么来,弟兄们过来看。U and点加吧,那么这是不是有一个按点CPCPP是不是C语言的,从加吧再到C加,OK过来。这儿到这儿啊,由于这个open jdk这个呢,目录特别特别的长啊,我呢也就提前给大家呢,翻查好这些源代码就不再翻了,课堂上节约时间,那么按c.CPC。大家看UN entry,调用UN类的时候,自动的会去加载UN的loads store和也就是这三个。
14:05
那么按A的包装类告诉你,就带着读屏障,写屏障主要是这两个,那么order access2个冒号,就类似于方法引用,那么读屏这就是acquire啊,写平这你看这有个什么release,那这又是什么呢?那么找到我们的order access这个类,那么来吧。它的定义就告诉你,我static加过来,Public能看懂吧,那么就是我们所说的。粗翻是两个,读和写,细翻那么二二得四,Load,读读写写,Store。读写load store写后读多load,哎,所以说最终我们呢,结合我们的底层Linux的源码,在这我们会发现。你写order这个漏其实占用的是什么?我们的acquire,那么这个acquire里面是啥意思呢?那么大家请看。
15:03
怎么着,如果是MD64位的CPU处理器或者是其他的,那么来同学们在这块MVQ底层汇编,直接给你加上安排的明明白白OK,所以说最终我们会明白咱们的。内存屏障粗分是两种,细分就是四种,那么这细分这四种就是我们这的load load store load store多漏好,那么这四个又分别是什么意思呢?一图胜千言,全部给大家搞定,下面是open gdk c语言的源码这四个,那么这四个屏障我们强调过了,所谓屏障都清楚。是不是拦腰截段中间本来这一条线,比如说屏障类型,这是一段,说明这是一段直立,就是一个屏障,那么加过来屏障之前的和之后的自然而然会有些变更和动作,那么大家请看屏障load load load1,如果在LOAD1和LOAD2之间断加上一个屏障load load读屏障,我们说明什么?要保证漏一的读取操作在LOAD2及后续读取操作之前。
16:13
执行一句话,先LOAD1,读完了load load再去LOAD2 OK,那么这个是不是保证了有序性,那么也就是说在这儿禁止指令重排LOAD2,不允许排到LOAD1的前面,一定是要保证LOAD1的读取操作要在LOAD2及后续读取操作什么之前,也就是说先漏的一了才能漏得二,不允许你编译器优化器自作聪明去给我指令重盘。那么这个时候由我人为的自己来控制,OK,这个就保证了我的程序的语义正确性和效果好。下面死多死多死多一断撞上了S多S多S多二,也就是说在S多二及其后的写操作执行前,要保证S多一的写操作已经刷新到主内存,这个也好理解,两个线程都有写操作啊。
17:06
那么现程又写操作,你也要写,我也要写,最好什么第一个人写完了才允许第二个人写,那么这个是不是顺序和可见性都能获得保障,你先用完了我再用,你看s store2及后的写操作我要想执行,在我执行之前要保证STORE1的写操作已经卡特已经刷新回到主内存,这个就是我们的store store,那么接下来这个叫什么load store,读完了以后再去写。二。即其后的写操作在执行之前要保证漏E1的读操作已读取结束,漏E1现在先读完了,我已经至少在当前这个版本下读到最新的了。然后。另起另外一个版本,那么这个时候你再去写,诶,所以说这个时候是先读完了才能写,那么这个呢,Store,那么就是保证STORE1的写操作已经刷到主内存。那么漏二及后续的读操作才能执行,这个就是我们传统上所说的写后读,先写完确认了,比方说int a等于五,然后B等于A,先对A赋值为五,再读A的值就是五,确定了,实锤了,再付给我们这个B,那么这样的话是不是保证了我们的可见和顺序?哎,所以说同学们要明白,所以内存屏障就是读屏障和写屏障以及这四大屏障。
18:30
Load load store store load store store load来保证我们的先后顺序,好,那么同学们,这个请大家一定要先明白什么是四大屏障。
我来说两句