00:00
我们来看啊,是如何保证可见性的,有什么用,怎么用,用在哪,那么来。首先,所谓的可见性是指这样的,前面已经多次说过,多线程在GMM模型下面。对主物理内存所存储的共享变量是要拷贝一份回到自己的私有欲,就是每个线程自己的工作空间里面,对吧,工作内存里面,自己工作内存里面修改完了再写回到主物理内存,所以说呢。我们的可见性就是指保证不同的线程对某个变量完成操作后,结果及时可见,也即该共享变量一旦改变,也就被改变了,所有线程立即可见,那么这个就是V的关键字的可见性,它有一个及时通知的功能。那么下面我们呢来看一下原理。代码说话,按例驱动,如果我不加Y是没有可见性的,程序无法停止,加了BY保证可见性程序可以停止,那么下面咱们呢,用一个案例来说明,那么这老规矩,节约时间,提前建好。com,哔哩哔哩lailes,那么现在这看看验证一下vlaile的可见性,那么来吧。
01:12
同学们,由于这个是may这个方法,所以说我们在外面呢,定一个普通的布尔形,那么这个时候同学们lawyer。它是个错,那现在啊,我们第一个线程T1。那。老规矩。搁到这儿。我们都见过。Come in进来了对吧?那么Y要如果这个flag是true的话,那么各位亲,你晓得的,那么这个T是不是就一直在这死循环这么转着对吧?那么好了,那接下来。我们呢,如果说他能打出这句话。说明他什么这块。是first被变更了,所以这次检查是force的话,马上就结束这段循环,可以打印出这句话,那么在这一块,那么就是说明什么,Flag。
02:06
被设置为。First。程序停止OK吧,那么只要打出这句话,就说明我们flag修改的时候,从true修改成false的话,马上被T1这个线程所感知到。好,那么下面。两秒钟,那么现在呢,我们有两个线程,一个是main线程,一个是什么T线程,由于这个是static main线程也能干,所以说main线程呢?两秒钟以后。就把它修改为错,哎,意思就是说,如果main线程你对这个变量做了修改,我希望你T1及时发现,及时可见,那么随着我们状态位的变化,T1这个线程也产生一定的。变更要么结束,要么是什么做另外一套操作对吧,那么好这一块呢。我们呢,这个main线程已经把这个flag。
03:02
改为了这个,呃哦,改为应该是force啊首握,那么所以说被设置为false,那么改为force以后,它这是不是应该停了,好那么在这块我们呢,这是什么,这个面线程修改完成,OK,来同学们,我们来跑一下我们这个程序,一跑我们呢就会发现。结果是这样的。同学们T1。Come in main线程修改完成,所谓的修改完成就是一定是走到第29行,那么走过29行以后,27行也应该过flag已经从原来默认的处设置为了false OK,同学们请看,此时灯根本就没有灭。一直在这儿。转着,说白了就是说。Flag。已经从true修改为false以后,T1里面根本就没有感知到,没有人来通知我,OK,好,如果说你觉得还是有疑惑的同学啊,不妨呢,我们再跑一次啊。
04:03
那么加上。这个flag的值啊,来看看没线程它到底改没改,那么来同学们,我们再执行一次好。重头再来,T一进来了,说明T1转着了,Main线程修改完了,Flag是不是已经从默认的处修改为false了?可是此时我们内线程打完收工走人,Flag我认为改完force,但是抱歉根本没有刷新回主内存。通知其他线程T1还是使用着之前自己首次所取的这个错,可不就是一直在这转嘛,所以说大家可以看得出可见性是什么意思了吧,对对方对其他线程根本不可见,对方没有收到你的及时通知啊,所以这种情况下是非常危险的,那么对于变量之间的状态变化的协作,你必须在高并发下面你要加。我们对应的。
05:01
来保证程序的可见性,好,那么同学们我们如法炮制啊,再跑一遍,就是在这个flag上面加了个V,来同学们来看一下我们的程序还会不会依旧卡顿,那么来同学们。两秒钟。进来了,修改完flag为false,马上打印出这句话,Flag被设置为false,程序停止,哎,所以说同学们,现在通过这个案例大家应该发现这块灯绝对已经消失了,说明什么?这个Y循环已经。出来了,否则这句话是打印不出来的,程序停止,OK,所以说同学们现在理论实操,那么现在叫小总结,所以。不加Y没有可见性,加的在保证可见性,那么上述代码我们已经写完,那么底层原理我们再说一下。来,同学们,线程一为何看不到被主线程就是这个main线程修改为force的这个flag的标签值呢?那么问题的原因呢?可能有两种,第一个主线成面修改了flag之后,没有刷新回主内存,所以T1看不到。
06:11
第二个我呢主线程呢,刷了卡到主内存了,但是你T1一直自娱自乐,根本不管最新情况的变化,就说我有没有这种机制,有没有收到过这个消息,告诉有人通知我这个最新值已经变了,所以呢,T1一直读取的还是自己工作内存中原来那个flag等于处的那个值啊,没有去主内存装。更新获取flag最新的值。好,这是问题的可能。那么我们的诉求是什么呢?现程中修改了自己工作内存中。副本之后,希望你立刻刷回主内存卡,密到主内存,OK,第二个工作内存中,每次读取共享变量的时候,都要去主内存当中重新去读最新的,然后拷贝到工作内存当中再开工啊,一定要明白工作内存是每个线程自己私有的,主内存是那个共享变量大家共有的,OK,那么所以说我们的解决第一个。
07:13
我们用来修饰这个主物理内存里面的这个。布尔值共享变量就可以达到上面的需求和效果。那么贝维拉特尔修饰的变量有两个特点,第一个线程读的时候,每次读取都会去读内存当中读取共享变量的什么最新值,然后将其复制到工作内存。第二个线程中修改了工作内存中变量的副本,修改之后会立刻刷新回到主内存,哎,所以说这个叫即时可见性,那么来,那么再读读这句话,即时可见该共享变量一旦改变,所有线程立即可见好。那么最终,那么再来,同学们。我们呢,大家呢都会要明白这么一张图啊,小鸟小鸟过Java内存模型当中定义了八种。
08:03
每个线程自己的工作内存共和主,物理内存之间的原子操作主好,那么分别怎么来的呢?注意为什么八部里面前六部啊。杨哥要把它标成蓝色,后两步标成红色的走读取加载,使用a sign,副持store存储write,写入look and look来吧,那么下面就要来整个完整的闭环流程是这样的,那么同学们先看一眼。八步。不是天龙八部啊,那么操作主内存是占面,下面是工作内存,再下面是CPU,我们说过了,CPU要比。主内存是要快的,对吧,那么这个时候计算归它,存储归它,那么下面来吧。假设。我们这有一个主物理内存,有个共享变量,布尔值flag等于什么错,默认的对吧,现在呢,某一个工作的线程,比方说T1这个线程先取read的读啊。
09:08
从哪读啊,从主内存这儿,注意必须成对的,什么循环出现,读完了以后就加载进线成自己的工作内存。第一步,读取。第二步,加载read load OK循环成对出现,加载到了以后说明什么?假设这个布尔值是个错,我加载过来了,然后我就使用嘛,对吧,然后我一使用咋地这块是不是个错,那么。转着OK,好,那么类似于这样啊,然后呢,我就使用那么完了以后假设。我就在这儿一直转着了,那么另外一个就是main线程也过来,这样从主内存当中读取出来,默认值是多少,是不是出啊,出了以后加载进来,这个是main线程的工作内存了啊,那么这个时候继续使用它呢,就是我们的错,完了以后,用完了以后它准备。要修改了,那么CPU负值啊,我要把true变成first,那么这个时候副值要执行A。
10:06
使用完了main线程就要做赋值,那么比方说你自己业务逻辑要配合CPU进行一下修改,完了以后修改以后干嘛。速度存储,听到那么自己内线成的这块工作内存里面,其实这个布尔值啊,已经从默认的处修改,为了什么force死了,好,我自己这块,那么我现在接下来准备度。写回主内存好到这儿。可是问题是写的时候,这个写操作也有可能别的线程再读啊,那么别人要读我这个main线程要写回去,为了保证数据的安全和一致性,写操作是一定要加锁的,所以在这儿我就lock锁定,然后呢按lock再解锁,注意。一旦我要取加锁注意,就相当于说main线程要准备把。我们的修改写回主物理内存了,那么这个时候我要写进去的时候加把锁,我去卫生间我肯定要拉上门栓嘛,对吧。
11:07
加锁号必须解锁,解锁前必须加锁,OK,这个呢,不说洛克按洛那么重要,是这。加锁后会清空工作内存变量的池就是其他现成的。手上持有内发。作废,那么请你们在使用变量前必须重新load或听懂重读一份,因为我已经把它修改了,只要是白了才有变量,OK,那么来吧。那么现在没线程加完锁以后很快的啊,就是线程级别的,那么马上把主内存中的这个布尔值啊,由原初的初啊修改为force,写完以后立刻解锁,那么由于你去加锁了以后。完了要修饰啊,那么别的线程T现也就会明白我之前读取的这个怎么着,我工作内存里面的这个已经作废了,我的工作内存中变量值啊,在使用变量之前需要重新去漏的,那么一重新漏的发现它已经从原来的错变成false。
12:08
1FALSE,我T收到最新的值啊,那可不是设置为false程序停止了吗?这么说OK,那么好,那么同学们,这个就是我们的八个步骤,我们再来。Read用于什么主内存?一步步过啊,Read漏,那么从哪读啊?从主物理内存读到那个共享变量,将变量的值从主内存传输到什么主到工作内存,主内存到工作内存第一步第二步,漏用于谁工作内存?将上一步读过来的主内存传输的变量放到工作的内存变量副本,就是我们刚才所说的每个线程自己的内份器数据加载漏到,这是第一组,第二组use a sign,那么现在你加载过来了。那么当工作内存的变量传递给执行引擎,每当进位遇到需要改变的自建码指令时就会执行,对吧?那么我就该怎么用就怎么用,对吧?
13:04
然后呢,用完以后我肯定要给你个反馈的输出结果,比如说我的业务逻辑就是要么是状态变更,要么是数据计算重新来了,那作用于哪。工作内存结合CPU把我自己线程里面的东西给改了,就是自己每一个线程自己私有这块域啊,工作内存里面的,那么将从执行引擎接收到的赋值,赋值给这个工作内存的变量,那么遇到这个以后,自节码也会执行一下你的操作,这是第二组,那么也就是在读取来了以后,结合CPU现成的工作内存里面,你自己爱怎么用就怎么用了,嗯,下面第三组啊。和right。你自己的工作内存S复制完了以后,是不是必须要存储好,就是在自己稳定了,在自己线程里面用于什么工作内存线程自己的复制完毕的工作变量准备写回给主内存。最后,Write作用于谁主内存?将store传输过来的变量复制给主内存中的变量,那么好了,上面就是完整的六步,那么为什么会加了这些呢?那这个就是结合我们的键面末和总线嗅探机制,缓存一致性协议。
14:13
来。上述六条能保证单条指令的原子性。就是你只要不是。高并发多线程,哎呀,你就顺序执行没事儿,但是。一旦是高并发的,有多条指令了,那么这个时候大家都要来抢了,你不加锁,那么这个程序是一定死翘翘的。所以GVM提供了另外两个原子指令,那么就是我们的lock lock。作用于神主内存将一个变量标记为一个线程独占,那么大家都不能说是。回写的时要我要写回去确认的时候,对吧,我写的时候别人也在写那个程序不就乱了,不就是线程安全问题了,所以说这个时候将变量标记为一个线程的独占,只是写的时候加锁,那么锁了写变量这个过程慢变更的一会过程,就比如说我去卫生间拉上门刷。
15:05
示范内存翻遍完了马上解锁门栓,我自己再出来,很快的一个过程,那么这个呢是加锁,这个呢是按lock解锁,那么每一个处于锁定状态变量示范以后,然后才能被其他线程占用,或者说是什么再改写,哎,所以说这个就是结合我们的GMM规范,那么V拉的变量的读写过。
我来说两句