00:00
接着呀,咱们来看最后一组叫做同步控制指令同步方法,对吧,这个咱们同步代码块,这个咱们都应该听过,就是说呢,我们这有个方法,这个方法内呢操作,比如说当前类中的这个属性,那现在呢,我有多个线程呢,现在要调用这个方法,那就意味着多个线程呢,都要操作当前这个属性,那关于这个属性的操作呢,有多行代码执行,那为了保证这个操作这个属性的过程当中啊,不产生现成的安全问题,我们需要考虑呢,叫做同步。那这就是同步机制的一个基本情况对吧,那么同步的话呢,我们说有同步方法和同步代码块两种情况,所以在咱们这里边儿呢,也对应的就分成了两种情况,首先呢,咱们来看一个简单的一个概述。说Java虚机呢,支持两种同步的结构,方法级的同步就相当于我们对应的叫同步方法,还支持呢,方法内部一段指令序列的同步,那就是我们在方法内使用的同步代码块,那这两种同步呢,都是使用的叫做monitor来支持的,这个monitor呢,咱们翻译过来就叫做监视器。
01:05
哎,也称作呢,叫同步监视器,对吧,通过它呢来进行一个控制行,那首先呢,咱们来看一下,叫做方法级的同步,也就是我们所谓的同步方法。这里边儿呢,首先提到说方法集的同目呢,是隐式的。什么叫隐式的呀,那这块呢,其实也稍微写了一个例子,这也是咱们一会儿呢要讲下这个例子,这个例子当中呢,咱们把这个爱的方法声明成是一个S了,就是同步的,但是呢,我们看这个字解码这个指令的时候呢,你看不到任何关于S的这个身影,你把这个S去掉,然后再进行一个编译,进行一个解析,还是长这样子。那就相当于呢,我们把这种情况呢,就称作呢,叫做隐式的。就是说诶这个没有在这个四码指令这块,我们看到这个SNE的对应的这个指令了,但是呢,并不意味着咱们这个方法可可不是说呢,加不跟不加都无所谓,这个是不一样的。
02:01
因为呢,这个西红袋子呢,也看作是咱们这个方法的一个描述符啊,或者这个准确来讲呢,不能叫描述符了,叫叫访问标识,这个呢,就涉及到咱们讲的这个第一章当中,回忆一下第一章当中,咱们讲class文件结构的时候呢,提到了这个方法对吧?方法里边呢,你看有方法表,方法表的话呢,这不就有具体的一些这个具体的信息吗?涉及到了一个叫访问标识。那这个咱们就别把它看成叫描述符了,那其实也是一种描述对吧,只不过呢,把这个访问标识呢,单拿出来,那在这个访问标识当中呢,我们就看到其中有一个呢,叫做sized对吧。那没问题的,相当于呢,就是如果我们一个方法是一个。诶,这个,呃,打开这儿,如果我们说一个方法是一个同步方法的话呢,它会在访问标识这块呢,加上一个叫S。那那咱们就在这块呢,说到这儿呢,咱们就来看一下,打开一下咱们这个同步的这个指令的一个测试,那刚才呢,咱们提到了,其实就是这一组操作没问题对吧,那现在呢,我做一个编译。
03:08
然后这呢,我做一个view展示这块呢,我们来看一下,咱们看到这个叫爱的方法对吧,打开,诶这就是我们说的这几个子解码指令,这个你把它这个新链删掉之后呢,其实这块呢还长这样子,但是区别的点呢,就是关于我们这个方法的这个描述,这块大家会看到在这儿。你看在这块呢,是不是有一个这样的说明,对吧,这呢我给你盯一下。好,这我就定了一下,那现在呢,你把这个信用袋子,比如我们去掉它。当然这个我们把这个呢,你也可以给他也盯一下。好在这那我现在已经去掉了形了,咱们重新再做一个编译。我这儿呢,做一个刷新,刷新完以后呢,大家你看这个时候跟我们这个最起码这个指令这块你看是完全一样的,没有任何区别,对吧,那我们再来看一下关于这个方法描述这一块呢,你看已经没有这个叫了,这呢是我们叫访问标识,所以说呢,区别呢,不是在这个四千八指令这一块,而是在我们这个方法的访问标识这一块。
04:07
那当我们一个方法声明成是一个同步方法的时候呢,它就是一个隐式级的一个同步机制,那无需通过自检码指令来控制它实现呢,在方法调用和返回的操作当中,虚拟机呢,可以从方法这个常量池当中,结构表中就是我们说访问标识这个指令是吧,来看到它是不是这个同步方法,那当我们调用一个方法的时候呢,会检查它有没有这个标识,那如果设置了呢,那在执这个线程,在执行这个方法的时候呢,我们要判断一下这个,先判断一下这个是不是持有这个同步锁再去执行,那如果呢,你要持有这个同步锁,就是别人还没有拿到,那你先拿到这个土木锁了,那你就去执行,在执行完以后呢,记得要释放这个土木锁。那否则的话呢,这就相当于是一个叫什么呀,这个阻塞状态对吧,那别的线程也进不来,全都阻塞,这这是不对的。
05:01
啊是,或者我们也称为这像叫死索的这个场景,对吧,那在方法执行期间呢,执行线程它持有类的同锁,任何其他线程呢,都无法再去获得这个基本知识,大家都应该懂,那下边呢,说如果有方法在执行过程当中抛出了异常了,那在这个方法内幕呢,如果无法处理这个异常。如果无法处理异常,那这时候呢,我们这个方法呢,肯定要结束了,对吧,就要要要通过。抛异常的这个方式,这个方法呢就终止了,那终止的时候呢,它也一定要把这个,呃,我们对应的这个同步监视器,或者我们叫这个监视器,你得给人家释放了啊,也要把这个去释放啊,这个大家一定要注意一下这个情况,行这呢就咱们说的这个叫方法级的这个具体的这个同步,哎,方法级的具体一个同步。啊,虚拟机呢,是不是还是这个事儿,当我们判断一个方法的时候呢,在它的访问标识当中发现了这个重目了,那我们就会自动的给它去加锁。别人没有握锁,你就加锁,如果别人握锁了,那你就等待是吧,这个时候呢,我们这个monitor enter monitor就是一个是进入这个握锁,一个是释放锁,这个操作呢,就不用我们显示的来写了,所以这块呢,我们看到这个代码层面呢,似乎没有什么具体的这个,呃,可以去过多的去讲解的。
06:17
那么要想把这个同步这个握锁和释放锁说的更清楚呢,那这里边儿呢,咱们就得举一个同步代码块的例子,那对应的呢,就是咱们这里边儿讲到的第二种情况,就是在方法内指定的这个代码序列呢,我们使用了这个同步。嗯,使用这个同步,嗯,我们来看这说呢,如果我们在这个Java方法当中呢,使用C语句来表示的话呢,我们在这个指令集当中就会有两个对应的叫monitor enter和monitor,一个呢叫握锁,一个呢叫释放锁,这两条指令呢,来支持这个形态的这个关键字。那下边呢,就是具体的一些描述说,当一个线程进入同步代码块时,它会呢,用使用这个呃,Monitor enter指令来请求进入。
07:01
如果呢,当前这个对象的这个监视器,它这个计数器的值是零,如果这个计数器的值是零的话呢,则被允许进入,那若为若是一呢,你看一下是不是自己握着的这个同步加然器,如果是自己的话呢,那也能进入,如果不是自己的,那是别人握的,那就是等待让别人呢,这个呃先去操作对吧?那直到呢,这个别人执行完以后呢,从零变成呃,从一变成零了,那这个时候你才可以去抢对吧?就好比是我们现在这个场景,这是一个显然器,三个线程,那此时呢,这个监视器,这个我们对应的是一个对象,对象的那个监视器那个值是零,那他们仨呢,谁都可以去抢,假设线程一抢到了,把这个零改成一了,那线程一呢就进去了。好,我这里边有个线程,假设那里边没这个线程啊,那线程一呢就进去了,那线程一进去之后呢,这个二跟三呢,一看诶这已经是一了,他俩呢就都得等待,当我们这个一执行完以后出来的时候呢,它有一个叫monitor e,那它要把这个一呢再改成一个零。那它再出来对吧,那这个时候呢,我们这个线程二和线程三的话呢,诶看到已经变成零了,他俩呢,诶都可以再去抢,假设三抢到了三就进去了,然后三把零改成一,二呢还接着等,就是这样的一个场景,保证呢,就是我们里边操作这个,呃同步代码块,就是里边那个共享的那个同步代码块,里边那个数据呢,保证在同一个时间段之内呢,只能有一个线程来进行操作。
08:24
那这样呢,我们就保证这个数据的一个安全性,对吧。哎,是这样一个场景行,那具体这块这个细节呢,咱们就哎不多在这说了,咱们来通过这个代码呢,来进行一个分析啊,就是我们下边写的这个场景。那么这里边这个obj呢,就是我们通常叫做同步加然器,对吧?啊叫做同步加然器了,好,那这个代码呢,咱们把它刚才呢,这不是也这个也没改,它直接编译过了,咱就直接来看这个subtract啊看这个code,那这呢看着还挺多,咱把它的CTRLC粘出来呢,咱们分析。
09:02
CTRLV一下。这个呢比较长,咱们给它截一截。嗯。嗯,行,这个调不成一行了,那咱们就这样也行。好,那咱们来看一下这个操作,对应的这个代码的话呢,咱们也给他盯一下。嗯,好过来了,来咱们来分析一下,看看这里边这个代码该如何呢?指令该如何去执行,首先呢,这个叫LO0啊,那首先我们看这个方法呢,是一个非静态的,这个位置呢,相当于就是个Z此对吧?啊这个Z次那对应的这有个具体的地址,比如0X1111啊四个一就是当前这个subjecttra这个方法所在类的这个对象啊这个对象那我们在这儿呢,加载进来0X1111。
10:00
那它其实对应的是我们说这个这次啊,那进来之后呢,然后我们去获取叫get build,获取当前你这个对象的叫OJ这个属性,那获取这个属性它跟着也就出去了。嗯,他出去了,然后把这个属性呢,加载过来叫obj对吧,就是我们当前这个类的一个实例嘛,Obj,嗯,行这个就进来了,当然这这个这个obj应该写这。这块呢,应该写你人家这是个引用类型的对吧,所以这块呢,应该写你那个地址值吧,比如说0X2233行,这个我们在对空间中你用了个对象,然后呢,那个地址值这块呢,我们就记录的是0S2233行。所以说你这个对象什么时候new的这个呢,是在涉及到我们这个,呃,实例创建的时候呢,你这个初始化的过程当中,我们去这个创建的这个是咱们调研方法之前的一个环节了,咱们就现在就不关心,好接着往下走。嗯,我们这个get field拿到这个obj之后呢,下边有个damp,就是把这个obj呢再复制一份。
11:01
行,复制这一份。在那角a store呢,下线一保存在这0X2233。嗯,行,这就保存了对吧,那保存的时候呢,这块就出去一个。嗯,没问题,然后呢,保存了以后,下边呢叫monitor enter啊monitor enter这呢,就我们需要进入这个监视器,这个监然器呢,是针对于谁呢,是针对我们这个叫OBJ2233来讲,这个呢,我们通常称为呢叫同步加然器,这个对象大家还记不记得咱们在当初讲。上篇的时候找一下咱们这个上篇的这个思维导图啊。咱们再讲上篇的时候呢,讲这个对象的内部结构。找一下我这个图放哪儿了,在这讲这个对象的内部结构的时候呢,大家还记不记得对象的内存布局,有一个对象头叫hier对吧,然后这个头当中。嗯,这个掉线了,好了。在这个对象头当中啊,我们是不是有一个锁状态的一个标识,那记住了是哪个线程持有的你这个锁对吧?这个标识的话呢,呃,就是我们对应的这块呢,提到这个标识的问题,那如果呢,是我们首次来这个握取这个同步监视器,那一开始它对应的这个同步监视器,或者叫这个监视器的那个计数器呢,只是零。
12:21
那我们当调用这个monitor enter的时候呢,假设这个线程进来了,那这时候呢,对应的这个obg,那个heer里边的那个值呢,就从零改成一了,啊这个在对空间里边那个还点从零就改成一对吧,那这个时候呢,你使用了这个对象了,它呢就也出去了。那这呢,就是我们这个monitor enter,它这样的一个操作,哎,咱们刚才呢,就是说的是这个操作的情况,行,然后呢,接下来叫lo下边零,把这个0S1111就拿过来。嗯,他拿过来,然后再把它呢复制一份。复制一份,然后再接下来呢,获取它这里边的一个I,那获取它这里边儿的一个属性I,咱们在没在这儿,是在代码里边,这是吧,也是一个属性,那这块你要用个属性这块就得出一个对吧,你看出一个之后呢,这块属性IA属性I,咱们得把那个值得拿过来吧。
13:17
I的值,如果你前面没有做过什么修改的话呢,这个值就应该是零对吧,所以我这块呢,写个零,它其实对应的是I。嗯,行,那他又过来了,找到这个值,然后接下来呢,叫ICONS1,那我们往这个操作站里边呢,放一个常量一,接下来呢叫I sub,就是它俩做一个减法,对吧,减法呢,就俩都出去了,出去之后呢,呃,零减一,那不就是负一嘛,那负一呢进来。接下来呢,再put field,那put field呢,就是把我们这个是不是就当前你这个Z把它这个对象的,然后呢,叫I的这样一个属性是不是复制为负一啊。那因为咱们这里边让这I减减了嘛,就是从零变成负一对吧,所以这个负一呢,就也出去了,这个对象的I属性赋值为负一,这两个就都出去了。
14:05
好,那么我们现在呢,就执行完这儿了。接着再往下呢,我们这有一个叫lo的下划线一,那就是我们这个2233。嗯,他进来,他进来以后。那他进来以后,下边一个叫monitor X,那这时候进来的目的呢,咱主要呢,是现在又要调这个monitor了,就是把这个对象。其实就是我们这个obj对象,人家那个hier里边呢,关于那个监然器的计数器呢,刚才呢,是不是从零,在这个操作的时候,从零变上一了,但现在你要执行到这儿的话呢,有一个叫monitor X,那就把人家那个一呢再改成是个零。哎,再改成是个零,所以这块呢,它的就也使用了,也出去了。这就相当于把它做了一个调用对吧?行,那出去完以后呢,接下来执行这个27,呃19 19呢,叫GOTO27,那直接就结束了,相当于就是我们刚才的这个操作对吧?这个同步代码块中的这个操作核心的内容呢,就是咱们将这个变量I呢,是不是从零变成了。
15:06
哎,当然这个负一是临时的啊,我们又得是调这个当前对象的这个I的这个属性,然后改成负一,主要是给你对空间中这个属性I呢做了一个值的修改,对吧。在这个过程当中呢,我们这个需要去调用这个O接,然后让它呢,有一个呃,就是进入的时候改成零变一,这个零变一的过程很重要,当我们这个线程指定的第一个线程,比如说把当前你这个2233呢,从零改成一的时候呢,这现在呢,我们就能记住是哪一个线程改的。那你看我们这里边是不是也有标识是线程的问题对吧?那那当你是零变成一之后,另外一个线程如果想过来发现你是一,还不是你当前这个线程一的话呢,那这时候别的线程都进不来,那当我们第一个这个线程在执行到这块monitor X的时候呢,它从一呢又改成零了,那它就相当于是结束了,对吧?退出这个同步代码块了,那下边你要有代码的话呢,它可以执行,但是这时候不影响别的线程去握这个锁了,那别的线程这块如果发现已经变成零了,它就接着呢,诶可以调你这个对象,把那个监然器计数器呢从零变一,那人家呢就接着开始进行同步操作了。
16:18
行,这样就是我们说的这个过程,那接下来的话呢,我们会看到这里边儿是不是还有这样的一块代码呀。还有这样的一块代码对吧,那这块代码的话呢,大家来看我们会发现呢,在咱们这个subtract这里边儿,虽然我们没有写这个对应的异常,但是呢,你看这个异常表。哎,大家看这个异常表呢,是有这个内容的。来我们分析一下这个异常表的内容呢,首先是七到19,七到19就是到这儿对吧,七呢就是从这块儿开始。七到19大家能发现这个特点吧,七呢,就是相当于咱们刚进入这个同步代码块,对吧,已经把这个呃,加然器计数器呢,从零变成一了,因为已经调用这个操作了,那在我们七到19的这个过程当中。
17:09
这个过程当中,如果要是出现异常了。对吧,如果要出现异常,出现什么异常啊,Any,任何异常都行。那咱们这个程序当中,除了咱们手动出现异常之外,是不是系统还可能自动的出现一些异常,就是你这里边代码要写的不靠谱的话,对吧,那这时候呢,不管你出现什么异常,我这块呢都可以handle,那憨的话呢,那你就往22走,那相当于就走到这儿了。就是呃,你任何一个异常类型我都可以来接收,接收的话呢,呃,为什么都接收,你不接收我们这块呢,是不是刚才握着锁了,你下边要是直接出现异常,直接出去了,锁没释放这坏事了,别的线程还进不来,对吧,那就麻烦了,行,那这时候怎么办呢?就是你出现任何异常,我们都跳到22。假设呢,咱们程序当中在执行这个同步代码块中的这个操作时候,还真出现异常了,那可能是任何一个环节咱不管了,只要你出现异常,就会生成异常的对象,把那个对象呢,注意store一下到索引二。
18:09
所以这个位置呢,其实是咱们这个我就泛泛的写了啊是exception。Exception这个就具体的是异常,不一定是这个类型的,可能是它具体的子类。啊,或其此类。这个实例对吧,但这个呢,显显然是不是应该有个地址啊,比如说叫0X3344,举个例子啊。好,这呢,就是我们这个当前你得到的。这样的一个异常的一个对象的地址,行,那有了这个地址之后呢,我们接下来叫lo的一个一啊这呢就相当于我们保存到这儿了,接着呢,叫lo的一个一,一不就是它吗?这个0X2233,这个我就往上写了。0223进来,现来以后呢,叫monitor X,现在我们要把当前,因为咱们握这个同步加然器的时候呢,是用的是022233,现在呢,你出现异常了,先把异常对象先保存起来,然后呢把我们这个同步监视器,就是当前这个OB接把它的那个对应的这个监视器计数器啊,因为你前面是一了,然后需要呢,在这个时候ex推出改成零。
19:19
啊,关键我们再把它加载过来,就是要改那个显然器的计数器啊改完了,那这个呢,就是这个事儿对吧?哎,这个操作完事了,完事以后呢,再把咱们这个LO2这个加载过来,那就是0X。3344来,这个他进来,他进来我们的主要目的呢,是要把它露出去。你这不也没处理吗?那就我们抛出这个对象呗,抛给你这个方法的调用者啊,这长就抛出去了,那当前这个战争呢,整个就结束了啊,Return是吧。那通过我们刚才这个分析,大家呢,你看到这里边儿有一个monitor。应该能理解,就是这个呢,是我们正常的你这个同步代码块呢,执行完以后,它呢要这个同步监视器,这个监视器计数器呢,要从一改成零啊,但是呢,也有可能你是不是一个出现异常的情况,那我们要保证你出现异常的话呢,是不是也要执行这个操作呀。
20:11
没问题是吧,那下边怎么还有呢?下边这块你看写的是22~25 22~25,也就是说呢,诶在咱们执行这个这几行操作的过程当中,假设又出现异常了,那我们还回到22这块呢,让它接着呢具有一个monitor ex,总之的话呢,就是一定得让我们当前的这个监视器计数器呢,从一改成零。啊,是务必要做这个操作的啊,就是这样的一个原理。能行吗?哎,这个大家呢,应该是体会一下,我们这里边这个用心良苦是吧,哎一定要保证咱们这个同步加湿器的这个数呢,要做一个修改,那关于这个零一是吧,这个问题的话呢,大家你可以在这儿打开,比如叫monitor enter点一下。这呢就调到咱们这个Oracle官方关于这个monitor enter,它的一个指定的一个说明,看这呢写的也比较清楚。
21:03
啊,你看说如果说进入这个count,就是当前你这个监视器。呃,当前你这个对象引用其实就是咱们当前这个文体的OB接吧,如果当前这个obj对象的这个监视器的这个,呃,计数器的这个值呢,呃是零。那我们这个线程呢,就可以进入你这个呃监视器了,呃,然后并且呢,把它呢,这个监视器的这个值计数器呢改成一,然后这个线程呢,就是你当前这个显然器的一个运营者叫owner,那它呢就可以进行操作了,说如果这个线程。已经有了当前这个监然器计数器了,基于你当前这个obj对象的这个监然器计数器,在咱们这个题目当中,不就是OB接嘛,是吧。如果你要已经获取了,那他呢,就继续执行,那继续执行就行了,那如果说另外一个线程,它已经获取了这个间然器计数器了,OB接的接然器接收器,那这个线程呢,就会blocks another thread,这个是the thread,这是就是呃,另外别的线程已经获取了,那你这个线程呢,就要进行一个blocks,就是阻塞,直到呢,这个监视器它的这个enter这个值改成第二的时候呢,你才TRY是吧,Try again,然后呢,继续呢,试图去获取。
22:17
哎,作为他的一个运用者,OK,说的非常清楚。好,那么我们回过来的这块呢,嗯,其实基本上我们要强调的事儿呢,也就通过代码的方式,咱们该解释的也就都说清楚了,这个呢,就整个是咱们说的这个臀部控制这块的一个指令。那这块的一个指令,那以前的话呢,咱们只是说啊,同步代码块中只能有一个线程执行,现在的话呢,我们就知道在底层这个代码层面,这个指令这个层面它是如何进行控制的,怎么能保证出现异常,它也一定能够释放这个同步监视器呢?准确的说应该是这个监视器,它的一个计数器从一变成零对吧,我们这块呢,其实有一个异常来帮我们去做一个维护,针对的还是任何的异常类型。
23:03
OK,没问题,好,那么至此的话呢,咱们这一章讲的呢,其实咱们说的也够细致的,主要呢,咱们就把这些指令呢,就带着咱呢全过了一遍,那这个讲完之后呢,大家以后再去看这个资金码指令的时候呢,应该就一点也不恐惧了。大不了呢,你就看一下官方的那个说明呗,对吧?行,那以后呢,咱们大家再去学习相关的一些代码的时候呢,当你对这个代码理解感觉不太透彻的时候,或者不知道它底层到底怎么来实现的时候,那看一下这个直接码指令,那很多时候呢,关于代码层面的一个优化。代码层面的优化,大家呢,也可以通过这个指令的一个执行层面呢,进行一个参考。啊,基于这个参考呢,回来再去优化咱们的代码,这也是咱们整个呢,想提高GM这个性能的话呢,其中的一个可以考虑的点,那就是代码层面的优化,这也是咱们夏天当中给大家呢要提到的,所以呢,咱们现在讲的这些东西呢,其实都是为咱们的夏天啊做基础打基础的是吧?啊这呢是咱们说的这个第二章。
24:09
那么下边呢,咱们再讲呢,就开始这个第三章啊。
我来说两句