00:00
好,那么各位同学,我们继续。我不保证原子性的案列演示,刚才我们已经给大家演示过。那么现在。再跑一遍,我们看17722。言下之意。我们经过前面的梳理和分析,知道20个线程。那么现在每个线程执行1000次啊,20~1000,最终这个值是不是应该是2万啊。但是呢,我们这个a plus这个方法我们知道的。故意的,我们一没有加深空的案子。也没有加洛克什么锁,那说白了,多线程来。访问他肯定存在线程不安全的问题。但是注意我们这个number加加目前可是添加了修饰的。根据我们的演示,干嘛你没有加synchize,就算是加了verlaile的关键词修饰veile,由于不保证原子线,所以我们一跑就会出现了丢数据这种情况,那么这种情况下说白了就是什么?根本加不到2万,永远是会比2万小,你看同学们,不管我怎么跑。
01:16
是不是都是。小于等于2万,当然。前面也讲过,有没有可能某一次刚好加到2万呢?这种狗屎运是有可能的,但是基本上大家请看,我们现在期望值是2万,但是跑了这么多次都没到2万,那么呢,说明什么?根本就不能保证我们的什么原子性。那。这个number加加在多线程环境下面是非线程安全的,那么首先我们先说解决嘛,最传统版的。那么这么一个情况,那么接下来。我们呢,就要处理这么一个情况。WHY?他为什么不能保证?那么好,首先我们最粗糙的一种做法,我们讲过,那么假设为了保证有些同学的。
02:06
复习我现在就加上真空袋子加同步了,我们都知道哈,我们现在主要讲的是八肽啊。Think在去年大家就已经学完了,那么加了thinkize,那不用讲某一时间课只能有一个线程进来干,那么这个时候大家看一下。值是多少?2万值是多少?2万再跑一次,值是多少?肯定还是2万。那么言下之意,大家多次体会老师这句话,什么叫轻量级的同步机制啊,说差点的话讲过了,这句话是不是乞丐版的?因为laile是不保证原子性的。那么接下来我们说过,现在这个问题解决了,能不能用真空袋子呢,你?理论上可以,但实际上而言,我们讲过一句话,不要杀鸡用牛刀,更不要高射炮打蚊子。你现在为了解决一个number加加这么一个问题,然后的话呢,你要加个size太重了,那么至于说解决方案,我们后面会跟大家讲,那么接下来我们要和大家阐述的是这么一个问题,这一讲。
03:13
为什么他没有办法保证原子线?而且为什么去了S以后,每次一跑的话,它的值都会低于2万呢?那么number加加我们都明白。我们呢,首先要知道number加加干嘛,它底层被分解为几个操作。三个。第一个从左到右先获得,也就是说一个线程。我们昨天讲过,上一讲我们讲过GMM内存模型了。在主物理内存有一个值,现在假设number是零。那么好,那么同学们。这个是我们的主物理内存。假设现在初始值是零,那么OK。
04:00
现在。我们有三个线程要来读取并操作,甚至是多个,我们这儿说少点就是三个吧,那么这个是一号。那么这个时候是我们的二号,那么这个时候是我们的三号线程那么好,那么接下来。我们昨天讲过,主物理内存是共享变量,所有线程共享。那么这个时候开始,他们三个唰唰都拿到了同一份,拷贝到各自的现成的。工作内存里面再次强调。主内存是独一份二,就是我们的那个内存条里面的那个数据,但是每个线程你是不能够去操作这个主物理内存的,你都要把它拷贝回自己的工作空间。所谓的什么。变量的副本拷贝讲过吧?这个时候,每个人拿着一个零。每个人拿着一个零,这是初始值啊,好说,那么现在当我的线程我调一次,这个加个一。那么这个时候什么情况呢?
05:01
将会出现一号线乘零加一,它在自己的工作空间里面是一,二加个一是一,三加个一是一。昨天我们讲过,只要线程。他对。自己工作空间的操作完成以后,他的提交产物是不是要把他的数据写回给。我们的主物理内存。对吧,那么这个时候出现一种什么情况呢?假设一种某一个时间时间段。一跟二同时读到了快照。这个时候它们的值都是零。那么正常情况下,一先写回去,这个是一了,那么二干嘛?是不是要拿到这个一自己再加一次,应该这个时候再写回来,这个时候的值是不是应该是二啊?这一步同学们没问题吧,但是非常抱歉,由于多线程竞争的调度关系。某一时间段,一号、二号线程他们两个读的都是零,现在他们要at plus plus这个方法,各自在各自的工作空间加了个一,准备把这个一写回去,将会出现在某一时间段,一号线程刚刚要写这个一的时候,突然被挂起了。
06:14
好,二号线程刷写回来是一个一。然后呢?他呢,通知其他线程,注意我们这是学完了,他要修饰的,但是抱歉太快了。其他县城还没有,他一写完,完了太阳以后,通知给其他县城。在还没有反应过来的前提下,极快啊,因为线程底层操作这个时候在这儿被挂起的这个一马上也写回去。这个时候导致什么,本身你是一一应该加两次,但是会出现这个一把上一个一又给覆盖了,出现了什么。丢失数据了一次,这么说能理解,那么现在就是我们来解释了,为什么。
07:04
数值少于2万。那么出现出现了。丢失血值的情况。那么这个时候不妨我们呢再从另外一个角度来看。来,同学们,我们这个my date,我们大家都知道现在是不是my date.java。那么这个时候,同学们。我们知道这哥们儿是不是my date?第二。Class,那么沿下支支线,最终这个后面我们都懂的这块,这个后面我们干什么呢?是不是会有我们的GVM字节码。没问题吧,那么言下之线,根据我们前面讲的GPM,我们来看一下。
08:02
My date如果这么一个加,加这么一个方法,它的底层字节码会长什么样?那么言下之意。我们呢?来看一下,由于这个字节码现在这个类写的过多了,我们呢,单独写一个T1。这个是不是很很简单。有一个vla int n等于零的一个初始值的变量。然后呢,Public。Number。佳佳。N加加,那么这个时候我们的爱的方法。我们这儿要。Java p这个看字解码的命令。这两个你用谁都可以。这个时候请看。那么这个命令呢,是老师已经把它集成进了idea,那么你这边如果没有配置,你是没有的啊,这块呢,我们讲过idea的使用,怎么来进行idea的这个集成命令的整合,那么这边前面杨哥介绍ID的时候已经说过了,不再多废话用。
09:02
Java p这个命令啊,那么这个就是什么编译汇编的命令,我们来看一下这个I,就跟我们前面所讲的。一般我们人写的是Java源代码,JVM读的是点class文件,那么我们进一步看看这个点class文件它又长什么样?深度的学习Java字节码。那么杨哥在讲GVM和垃圾回收那门课。详细给大家解读过字节码,那么这个时候干嘛?还记不记得这些字解码,那么这个时候杨哥是不是带大家详细的讲解过?那么好,你要读得懂这些通告,那么你的加吧底层功力就深了。那么好读一下。这个东高,也就是说我故意把这小段还有这个变量,为了方便大家好看,单独拿出来没问题吧,定义这个int,定义那个I number加加,那么现在这个I的方法,各位亲,就是这的I。
10:01
那么呢?A。漏的零,那么这什么呀?那么这个时候拷贝还记不记得我们这张表,那么查这些字节码,那么来同学们A漏零,从局局部变量零中专载引用类型变量,那么现在同学们都明白我们这个inter number等于零是不是初始化零的这么一个变量。然后呢,Du appmp我们再带着大家复习一遍,忘了无所谓,只要有杨哥这张表,什么都能查到,复制站顶部的一个自查内容,那么言下之意,账管运行我们加载一个变量,我们讲过一个类里面的这样的实例,变量是不都加载进八种基本类型,都在站里面。复制过来,请看。当我们进行number加加,N加加的时候,其实它是干了三步操作。第一个。先获得初始值,这个初始值对应着我们的这一步。就是。
11:00
你看。N这个I代表它是特型的干嘛,目前。它的值是零。然后呢?进行。什么这两步操作。把它读出来了以后。特型and底层意思就是说要加一次,然后先get,然后加个一。然后put什么意思啊,写回主内存。那么也就是说兄弟们,他这一步你看的是一行代码,其实底层的汇编字节码,它是有四行代码构成。请看杨哥的老婆。ADD是这个方法。在这块也就变成N加,加被拆分成了几个三个指令,那么也就是说执行get费的时候拿到原始值啊,我们参考这个图,假设现在同时有两个线程,因为我们并没有加。这个时候线程进来是可以疯抢的,听懂了吗?这个时候一号线程,二号线程,三号线程它们都同时。
12:08
抢到了这个N。N加加被拆分成三个指令,第一步,执行get,拿到原始的值N,这个时候T1 T2 T3。三个线程都拿到,值是零,第一步同学们能跟上。第二步。执行ii的进行加一操作,注意拿过来以后,这个加一操作并不在主物理内存,加一是在它各自的什么工作内存里面,就是TE线程的工作内存加了个一,T2加了个一,T3加了个一,那么言下之意,123如果三个顺序写回来,这个值是不是应该是三啊?但是抱歉。正常情况下是三,由于我们没有加完了tell,修饰了,也没有办法保证原子性。不保证原子性的意思就是我在操作的过程当中,可能有人加三。
13:02
那么接下来请看第三步。要执行po的费用的写,把累加后的值写回主物理内存,这是Java自解码的源码中的源码了,源码中的战斗机,这不可能错的,这是用Java p汇编以后的底层原始命令。当你put field的时候。刷刷刷写回来,正常写应该是。你先写,你先写,你先写,这个代表有序原子性,那么这个值就应该是三,但是非常抱歉,有可能这个哥们已经先写了,它的值是一。然后这两个哥们被挂起了,刚好他写完又特别快的时候,这两个又被唤醒了,没有拿到最新值又去写,那么就出现了我们所说的写覆盖有可能。本身的值应该是三。或者是其他二这样的值,但是写成了一一这样的话导致什么写丢了两次,所以说虽然说操作了三次,但有可能这个值还是就会导致我们最终的运行以后的这个值。
14:12
怎么着?低于我们的这个数。所以说我们一爬。这块就没有办法获得2万,因为很多值在什么put field这步写回去的时候,可能现场的调度被挂起了,刚好有没有收到最新值的通知,有这么一个纳秒级别的时间差,一写就出现了,写覆盖就把人家的纸覆盖掉了,那么也就是说。本来应该写二或者是三,H写进来本来是三,但是三个都写成了一,弄丢了两次,导致这个值永远低于2万。这么说,同学们能不能跟上?能听懂吧,好,那么非常好,那么这边我们呢,就彻彻底底解决了这么一些问题,所以说从字节码到。
15:06
线程的变量一定要注意,不保证原子性就会出现血丢失的情况。线程太快了,后面的线程刚好会把前面的值写覆盖掉,出现了什么?丢失血值的这种情况俗称血覆盖,本身你写一次是一,我写一次再加个一,应该是二,说话太快,你是一,我也把你的一又覆盖了,但是值这个值还是一,本来应该加了两次,结果只加了,操作是两次,但是呢,只是一个。干嘛?一顿操作猛如虎,一看原值还是一,这样的话,就出现了血丢的情况。好,那么呢,这个就是向大家解释了。为了干嘛?它为什么不保证原子性的理论知识好?那么呢,这点先给大家介绍到这儿。
我来说两句