00:00
A,第四个值是B。然后不停的死循环,一直不停的起两个线程。这两个线程,第一个线程干了一件事,叫A等一,X等B。第二线程干了一件事,叫B等一,Y等AOK。给大家十秒钟消化一下这小程序,这小程序已经已经没了,嗯。那。这个证明是一个反证法的证明。反证法。好,根据我们刚才得出的结论,你们认真听我说乱序啊。这两条指令之间,或者这两条语句之间,很可能存在乱序,为什么?因为这哥俩前后没有依赖关系,你发现没有?他没有依赖关系,那站在一颗CPU的角度,你随便乱序,你乱序来了没,没有关系,最终的结果是一致的,OK,这个东西呢,叫做as if cereal,这个我一会我来给大家讲这个概念吧,很可能好多同学见到过这个词,但是不明白什么意思。
01:09
不着急,一点点来,总而言之,言而总之,这个小程序证明是这么来证明的,我们如果假设,首先假设不存在反正法吗?首先假设不存在乱序。不存在乱序,就会有一个效果是什么呢?A等一一定在X等B前面。X等于B,绝对不可能跑前面去。B等一一定在Y等A前面,Y等A一定不可能跑前面去。好。同学们。刚才讲的这小段还能跟上吗?能跟上的给老师扣一。你继续。这个你能听懂就听,听不懂就算了,听不懂就记着结论,乱序执行,这件事存在,知道这件事就行。那有同学可能就会说了,这这怎么证明啊证明我们来分析一下这两个线程,无论你怎么排列组合。
02:07
两个线程吗?他们之间有可能互相之间,你执行一会儿,我执行一会儿,无论你这几条语句之间怎么排列组合。排列组合什么样呢?第一种很有可能是这样,我A等一执行完了,马上执行X等B,然后第二个线程开始执行B等一等A,好,这是一种可能性。这种可能性,你计算一下X值几时是X值是几,Y是几?你会发现X等零。Y等于一。这是一种情况,当然还有其他的可能性,就是什么呢?B等于一跑前面去。Y等A跑前面去。A等于一跑后面,X等于B跑后面,这是第一个线程,这第二个线,第一个线程好,最终的结果呢,是X等一,Y等零,当然也有可能是打断的,比方说A等于一执行完了,另外一个线程执行B等于一,然后呢,X等B等A。也有可能Y等A跑到X等B前面,总而言之,言而总之,无论你怎么执行。
03:04
绝对不可能出现。X等于零,Y等于零的情况,你自己验算一下。我再说一遍,假如这几条语句不会发生重排序,你放心,X等零,Y等零绝对不可能出现。很有可能是X等于零,Y等于一,X等于一,Y等于零,或者是X等于一,Y等于一。嗯,李浩问了个初级灵魂的问题。李浩同学,你咋那么敏锐呢?这是指令级乱序还是语句级别乱序?这是语句级别。这种语句级别的乱序不太容易出现,但是也有一定的概率。语句级别,语句不就是好几条指令吗?好几条指令之间也有可能乱序的。我向你证明指令级别的论述,我只能给你写汇编语言大哥。你觉得我在这儿跟你聊汇编语言聊得通吗?虽然我的笔记里头写了汇编语言。
04:01
但是我要今天给大家聊汇编。咱们今天就别别讲课了。估计好多同学更懵了,虽然我的笔记里啊,跟大家聊了一些汇编。但是今天还是算了啊,这不汇编吗?用单内核带中间的0X80调用过程必须得用汇编来解啊。所以只要你语句级别存在乱序,你拿大腿给我想想,它指令级别存不存在乱序。嗯。对啊。好了,不说了。当然根据我们刚才分析的结果。只要我这个程序有一个X等零,Y等零的情况出现,那么一定就会存在乱序,跑一下试试。这个靠运气啊,有可能跑好长时间也跑不出来,有可能跑个十几秒就出来了。
05:01
呃。原因是呢,由于它指令级,由于它是语句级别的,得好几条指令同时乱序才能出现最终的结果,所以这个东西得靠运气了。我们大概以前执行过的,这是呃,272万次,执行完了之后出现零零,这个呢,11万次运气比较好,出现零零。我今天如果运气不好,就多等会儿,运气好的话呢,也许很快就能出现。太讨厌了,运气不是特别的好是吧?好吧,不说了。X的。为什么有同学还在这儿啊?出来了已经啊,运气不错,来看这里啊。呃,29万次的执行之后,出现了结果零零。所以这就证明了乱序执行啊,我看有同学说X等BY等A最先执行,不就都等于零吗?你这不废话吗?
06:03
你X等B等A最先执行不就已经乱续了大哥。好好想想。我们说假设不存在乱序,它才不会出现这种情况,出现这种情况就已经是乱序了,那你自己都已经证明了。你在想啥呢?老天这是反正法对。呃,不管你听听得懂没听懂,总而言之,言而总之,有一件事情存在,哪件事情存在呢?CPU的指令之间,有可能后面你给他写的这条指令跑到前面去执行。OK,得到这个结论的,给老师扣个一,我们继续注意,我给你讲那道题,你还记得吗?叫DC,要不要加VO这个,这里面好多东西呢啊,我只是给你讲了一个乱序,执行了我一点点来。嗯。好,再看这里。我给你讲完乱序执行之后呢,我还得给你讲一个概念啊。
07:04
看这里。呃,我给你讲完乱序执行之后呢,还得给你讲一个概念,这个概念呢,就是第一题就是解释一下对象的创建过程,先说结论,对象创建的时候有一个半初始化状态。呃,这个是什么意思,看这里啊,一点点来。来,我们来看这句话。Object o等于new object。这句话应该是最简单的一句话了,围绕这句话,美团出过九道题,这九道题得答出一个来都不容易,我们先讲第一题,也就是我们要讲的那道大题,需要的这个结论就是new object。这中间到底。有哪些个执行步骤?好。跑他一下先,哎,怎么第八个了,跑他一下先。版本有点问题,Rebuild一下。
08:02
跑一下。好,跑一下之后呢,我们来观察一下它生成的它那个class文件生成的字解码,关于自解码这个概念,应该不需要我解释,我觉得啊,那个就是呃,我们一个Java文件。编译完了之后呢,生成的是什么呀?By code,这个叫字节码,就是我们的点class文件。这个相当于Java语言的汇编语言。Java语言的汇编码。汇编码里边是怎么执行的呢?它是有好几条指令来共同完成这条语句,这几条指令是什么呢?看这里。我们来观察一下没?Show code with j这些小小的插件啊,我们用这个插件呢,来观察一下这条语句生成的哪些指令。Method。Man。好,看这里。
09:00
这是这条语句生成的指令。这里,Here。对。呃,一共有五条指令构成,当然这个字体比较小,担心你们看不清楚,所以呢,我就给它挪到了我们的PPT上。看PPT就可以了。OK。当我们写这么一个小小的。程序的时候就这个小程序啊,Class t里边有个成员变量M等于八。然后当我们写这么一句话,T小T等于6T6T的时候。真正执行了呢,是五条指令,这五条指令呢,分别是new指令、duplicate指令、invo special指令、A指令和return指令。在这里面有两条指令不太重要,我暂时也不想跟你解释。Return指令先不管它,Duplicate指令先不管它。这里面拗出一个对象来至少需要三步。
10:01
大家还能跟上吗?大家还能跟上吗?能跟上给老师扣一了,嗯,跟得上是吧?嗯,当我们扭出一个对象来的时候啊,至少需要三步,这三步是什么呢?打开冰箱,把长颈鹿拿出来。把大象放进去,关上冰箱。开个玩笑啊,开个玩笑好看这里啊,第一条指令呢叫new。第二条指令呢叫invo special,第三条指令叫a store,我解释一下这三条指令的意思,缪的意思是申请一块内存空间。用来装我拗出来的这个对象。还有这个空间已经申请好了,大家都知道扭出来这个对象里头一定有一个成员变量,这个成员变量是小M,那这个M的值是几呢?听我说,当你刚刚扭出一个对象来的时候,这个M的值是它的默认值default的值,这默认值是几呢?数值类型的都是零,Double类型,Flow类型,0.0。
11:09
引用类型都是空值。OK,所以最开始的时候,这个M的值是一个零。那什么时候它才会变成八呢?我不要求它等于八吗?什么时候变成八呢?调用完下面这个方法,这个方法叫in work in work调用的意思,Special特殊调用。调用了一个方法,这个方法比较特殊,哪个方法呢?叫initialize,什么方法?构造方法。调用了它的一个默认构造方法。这个方法执行完。这个M的值才会从零变成八。所以当我们定出一个对象来的时候,它里面的成员变量是有一个从零变八的过程,有一个从默认值变成初始值的过程。好,这个状态叫做半初始化状态。最后一句话叫a store a storere的意思是建立关联,谁和谁建立关联。
12:03
我们有一个局部变量小T,这个小T里面储存着一个指针,这个指针指向这个对象。好在只有在执行完这句话的时候,一个小T和我们真正用出来对象建立关联,通过小T才可以去访问它。呃,为了让大家看的清楚,我做一个小小的动画,大家认真看。当我们拗出一个对象来的时候,首先new执行到new这条指令的时候。内存里头是一个默认值的情况,M值等于零,当执行到的时候。它才会变成八,当执行到A的时候,它才会变成建立关联这个过程,所以你看上去简简单单的这一句话在会边界呢,实际上它有三条语句构成,它有三个步骤构成。好,听明白这个过程的同学来给老师扣一,我们继续。
13:04
有同学说第二行,第五行,大哥,我建议你呢,先踏踏实实的听完主干。这些细节是我的课上专门讲,每一个指令专门讲的,这给你讲完就没边儿了,好吧?加载链接初始化,加载连接初始化是静态的过程,Class的加载链接初始化,这是new的这个开始执行之后的对象的过程,跟那个差不多,好吧,好,不多说了。因为有一个每个同学的基础不一样,我们还照顾一下基础稍弱的。好,我们继续。呃,在刚才我讲课的过程之中,我第一步给大家讲了有乱序执行这件事存在有乱序存在,第二步给大家讲了当你有一个对象的时候。它有三步构成。好。还记得那道问题吗?那道问题是DC,单利要不要加vola?大哥,这里面有没有谁需要我解释一下什么叫单利的?
14:11
有吗?有的话老师扣个一。扣二吧。真有啊,好,我解释单利和DCL单例,你放心,我30秒给你解释完啊,你要听不懂,那就是你的你的问题了,我就不管了。关于单利呢,它是一种设计模式,这是另外一个,可以把这代码调出来。呃,23种设计模式,代码呢,我都带大家写过,但是单利呢写的比较多,一共有九种写单例模式的方法,单例你先说是个什么概念。单例的概念是这么一个东西,我有一个class,我只允许你溜出来一个这个class的对象不允许你new第二个。在内存之中,永远只有一个这个class对象,不会有第二个。
15:02
这是啥意思?来看这里,我有一个class叫man点零一,如果我想实现这个单例的话,我这么来这么来写就行了。OK,我上来二话不说,先定义一个对象,这个对象叫instance,然后把它给扭出来。虐完了之后,我不允许别人拗我的对象,我把它构造方法设成private的,那别人就虐不了了。所以谁想用我这对象的话,通过我提供的一个方法叫get instance。你只要调我这方法,我返回的永远是这一个对象。所以无论你掉多少次,这个方法最终你拿到的对象只有一个,Only one,这个就叫做单利模式,这也是单利模式的最简单的玩法。来关于这块能跟上的李老师扣一关于单利呢,我需要给你快速的过一遍啊。嗯,反射可以对,你说的很对,你说的一点都对。
16:03
看这里啊。作为这种写法来说,我上来二话不说,先定一个对象。那么有人呢,就会吹毛求疵。有人就会吹毛求皮是吧,嗯。好,谁呢?就是我们处女座的人。比如说乔布斯。比如说还有谁啊。不知道开玩笑啊,比如说我们处女座的人,嗯。他会追求每一个细节的完美。所以。当我还没有用到这个对象的时候,你干嘛要给我扭出来?你这不是浪费内存空间吗?所以。好,听我讲。因此呢,就诞生了第二种写法。第二种写法是什么呢?你能不能做到当我需要这个对象的时候,再给我扭出来?我不需要他的时候,你不要给我牛,所以第二种写法就是我首先定义一个对象,但是我不给他初始化。
17:06
什么时候初始化呢?有谁调我的方法的时候get?调用我这个方法的时候,我判断一下哥们儿,你是不是为空啊,你为空就是没有人把你初始化,OK,这时候我把你给初始化掉。最后返回给你。好了,这个呢,就呃。满足了处女座人的肮脏龌龊,不能这么说啊,满足了处女座人的想法要求。但是这种有问题。车有问题在什么地方?这个程序。县城不安全。为了向大家证明这一点,我呢,在中间睡了一秒钟,表示执行了一些业务逻辑。那么在这个情况下,如果你多线程去调用这个get instance方法的时候。他一定会出问题。考一下。
18:02
写,如体制著作a is not specified。看这里面。Key就是GPS我拷代码的时候的问题。一遍啊。嗯,来这。上一个版本的那个里面拷过来的。多台机器。嗯,我先不管它那个这小程序呢。呃,大家我不不用给你运行结果,你应该能想象的出来,是是会出什么问题,你可以想象一下啊,当第一个线程来到这里,访问这个对象的时候,发现为空吗?为空,好,第一个线程暂停,第二线程也来了,为空吗?为空,那第二线程来了之后呢,继续往下执行。
19:11
拗了个对象,拗完了之后,第一个线程继续往下执行,注意第一个线程已经判断完了,那直接又拗一个对象,所以这哥俩拗出来的不是同一个对象,因此这就不是单立模式了。好吧。演示也能演示,我给你拷过来重建一个项目就可以了,但是我懒了,偷个懒可以吗?代码太简单啊。好。那怎么解决这问题啊?太简单了是吧,解决这问题还不简单,加锁嘛,上锁。你上了锁之后,昨天我讲过锁的概念,上了锁之后什么意思,我只有持有这把锁,我才能去这个对象。只有等我拗完了,另外一个线程才能进来。我持有这把锁,我在里边噗噗噗便便,我便便完了,另外一个线头才能进来。
20:03
另外一个线程拿到这把锁之后,由于我第一个线程执行完了,我已经把它给扭出来了,你放心。第二个县城来判断它已经不为空了,那啥也别说。肯定拿到是同一个,所以。这个上锁肯定没问题,好这块还能跟上吗?能跟上的给老师扣一来。但是处女座的人又来了,处女座的人说大哥,如果你这里面有一些业务逻辑,你比如说你这里面写了好多好多代码,我们假设嘛,就是写了写了好多好好好多好多行的代码啊。Line等等等等,写了好多好多代码,这是你的业务逻辑。这些业务逻辑本身啊,其实用不着上锁。用不着说我持有锁的时候才会执行这些业务逻辑,没有必要。但是你如果这样的话,你锁定的代码块就太大了,这叫做锁的力度太粗。
21:01
叫做锁太粗了。所力度粗。锁力度粗就会怎么样,效率就会变低,只有锁的时候你要干很多事情,才能轮到另外一个线程,所以锁力度粗就会变低。那好,怎么修正呢?修正它的话很简单,就是把所细化OK。在这里。所细化简单了,就这么来做嘛。业务代码。有一些业务代码,然后没问题,我不给他上锁。什么时候上锁能判断哥们儿你是不是等空啊,如果等空上锁。Nchize man05class。上完锁。把你给拗出来,拗完了之后返还回去,好问大家一个问题,这个写法能不能保证线程的一致性?
22:00
县城安全能保证吗?依然不能。我看有人说能,我们分析一下为什么他不能。原因是啥呢?原因是你判断一下啊,第一个线程。直行到这里。判断instance等空没问题。第一个线程暂停。第二线程执行到这里判断注意instance依然等空。第一个线程还没把它丢出来呢,然后第二个线程继续执行。第二线程到这里的时候申请这把锁没问题,只有他一个线程拿到这把锁。开始执行,把MANAGER05拗出来,拗了个对象扔在这儿。没问题,然后第二个线程释放锁。退出。好,执行到这里的时候,第一个线程开始执行了,开始继续执行了。第一个线程开始继续执行的时候,开始又申请这把锁,我想问你这把锁他能申请下来吗?没问题,为什么?因为第二个线程已经释放了呀,你想想。
23:02
我已经把锁打开了,你第一个线程想锁上当然可以锁上了。没问题啊。我已经用完了,我已经变变变,已经变完了,该你了。既然该我了,那我就继续往里走,走到这里的时候,是不是又扭了第二个?所以。即便是你在这里上把锁没用。所以。就诞生了。DC的写法。下面我来讲DC。这个写法是这样子的。我呢,呃。一个线程来了,首先判断是不是等空,如果等空上锁,我上完锁之后,我再判断一遍,你是不是依然为空。刚才咱们那个过程不就是说在我上锁的过程之中,有另外一个线程已经把它给初始化了吗。我上完锁之后,我再判断一下,哥们儿,你是不是依然为空,如果你依然为空,说明没有人改过你,OK,那就完全没问题了。好,这个东西叫DC,全称叫double check。
24:10
Check一下,CHECK2下,中间夹着一把锁,Double check中间加了个lock,叫double check lock。但是他的问题还不是止于这里,他的问题是说哥们,你这个double check lock,要这种写法的时候,你这里要不要加volatile。这是他的问题。当然,我讲到这儿,大家应该明白什么叫double check,什么叫DC了,我相信大家呢,有一堆的问题,我一点点解解释给大家听。好,最基本的概念明确了的给老师扣一。OK,来看这里。
25:00
嗯。我相信大家呢,有这样一些个小问题啊,第一个问题就是说,老师你干嘛要写这个if等于空啊,你直接上来。上直接直接拿过来上锁不就完了吗?你外面这个衣服有啥用啊,有狗屁用吗?同学们,我们仔细分析一下,如果去掉外面这个if,会发生什么情况呢?假如有1万个线程。都来抢这把锁,反正他二话不说上来就synchize,那么是不是上来就锁定争啊?最终是不是只有一个线程才会把这个扭出来,也就是说剩下9999个竞争过程。必须得上来,不管你是不是你溜出来的。你得去参与锁定争。那这样效率就太低了,如果前面加一条if语句,会发生什么情况呢?只要有其中这1万个,里头只有一个new,完了。另外的人过来判断一下,我就不用参与所竞争了,效率就提高了,这种写法啊,在很多很多的开源软件里头都用到了,我希望大家掌握住。
26:09
没问题吧,嗯,第一个问题就解决了。好,我们来聊第二个问题,还有,还有其他问题吗?提高性能,对,没错,就是提高性能。好,继续。来,我们继续。第二个问题是他要不要加vola?我先把这个代码啊。给大家。对,给它切下来。切,最简单的最干练的就行了啊,放在这儿。然后呢,我们来看这个PPT。
27:00
嗯。好。呃,我们先说结论,先说结论。这个必须要加volatile,原因是什么?回想我前面讲过内容,我讲过程序是可以乱序执行的。第二个,你有一个对象的时候,是存在半初始化状态的。那好,下面我们来分析为什么必须要加。好,对照代码说。来看这里。假如有第一个县城过来了。判断,因此等空上锁判断它依然为空,然后就开始谬对象了。注意谬对象有三步,大家还有印象吗?New对象的话是这样来做的。第一个线程来了。判断为空,开始拗这个对象中间有三步构成第一步。
28:01
是申请一个内存,把这个值变成零默认值。好,我们假设它尿到一半的时候发生了指令重排序。认真看。后面这两两条指令发生了重排序。再看一遍。第一条第一个线程来判断等空没问题,上锁可以。开始拗对象也没问题,拗到一半的时候发生指令重排序。大家还记得这两条指令什么意思吗?这两条指令指令的意思是?这条指令叫建立关联,这条指令叫构造方法执行。那么他会先建立了关联。也就是说,这个小T已经指向了这个初始化了一半的对象,OK,正好在这个时候,第二个线程来了。来,你猜猜看第二个线程,第二个线程来会执行哪句话呀?是不是会执行if instance是不是等空这句话呀。
29:01
好,我就想问你这个时候这个T,也就是那个instance等空吗。能不能碰?不等空。不,既然你不等空,我还给你上锁去拿吗?我不需要上锁了,我就直接拿过来用了,对不对啊。那么你猜猜看,第二个线程用到的是一个什么状态的对象呢?他就使用了一个半初始化状态对象半成品。你做到一半的时候,他拿去用了这里的值,记录着双11我们成立了多少个订单,本来是100万了,不好意思,你拿走的时候他变成零了。Yes。Good。
我来说两句