00:00
好,那么这个同步完了以后呢,咱们刚才说了有两个问题,第一个呢,就是解决一下这个懒汉市的线程安全问题,第二个呢,就是来说一下,这个叫思索问题,什么叫死锁问题,那我们看一下以前呢,咱们提过一个叫死循环,就是接束不了了是吧,那死锁的话呢,就是锁住出不来了,哎,这叫死索,那这呢有一个说明,说不同的线程分别占用对方需要的同步资源,不放弃。都在等待对方放弃自己需要的同步资源,就形成了现成的词索,那这呢,就是我们这个死锁的一个解释说明。啊,出现思索以后,不会出现异常,也不会出现提示,只是呢,所有的线程都处于阻塞状态,无法继续,咱们讲线程的生命周期的时候呢,咱们这边提到了一下,说这个等待同步锁,这个时候呢,其实相当于是一个阻塞状态。那么咱们目前的话呢,同步锁其实就只都是都是一个。
01:04
呃,我指的一个呢,就是说,呃,当然了,咱们说的同步锁,大家得共用同一把锁哈,但是呢,就是我们可能是需要拿两把锁,甚至更多的锁才能够完成这个问题。啊,这个咱们等一下去举例子啊呃,那举一个生活中的例子,先大家形象点去理解,比如说呢,现在这个大家要吃饭是吧,吃饭的话呢,这这个桌子上放了这个好多这个菜,假设呢,咱们吃饭呢,都得用筷子啊,假设你就是你可能还有其他的一些方式是吧?啊假设呢,需要用筷子,现在呢,两个人啊,这是一个人,这是一个人,但是现在呢,只有一双筷子。啊,只有一双筷子,那正常来讲呢,就是说这个人吃完以后呢,这个人吃啊,那这时候呢,就是正常都能执行啊,这是挺好的,但现在的情况出现什么呢?这个人拿了一根筷子,相当于拿了一个头木锁,然后这个人呢,也拿了一根筷子,然后呢,两个人都在等待对方先把这个筷子给自己。
02:03
啊,这个人拿着筷子说,你先给我。这个男孩说,你先给我。两个人就在这僵持下来了,最终的结果呢,就是谁也别吃。啊,然后这种情况呢,其实就类似于咱们说的思索。啊,就类似于这个叫思索的情况啊,你也可以去说两个人,比如说互相有这个爱慕之心是吧,现在的话呢,大家好像都呃说一个我爱你好像都挺便宜的了是吧?啊天天说都没事啊啊那以前的那个时代的话呢,这个说句感觉好肉麻的是吧?啊谁都不好意思说,然后呢,都在等待对方的先去表达,最终的结果呢,就两个人错过了是吧?哎,好多这个电影里边都有这样的这个情节啊啊类似于呢,也是跟这个思索有点像啊,互相僵持着啊那么咱们呢,就可以举一个代码的例子来说一下这个叫线生的思索的问题。啊,我举个例子,大家看一看什么叫词索,去理解一下就可以了,你别呢下来以后呢,诶这个词索呢,挺简单的,然后写代码一些写个词索是吧,这个是让你避免的啊,你可别这个都写词所啊,那么咱们写个代码在这里边去新建一个,嗯,这个呢,我们不妨呢,就叫做thread的一个test。
03:18
啊,这呢,我们来演示呃,线程的这个死锁问题。好这呢,我就直接来这写代码哈,咱们呢整一个没方法,嗯,咱们呢讲这个线程的创建呢,有两种方式,两种方式呢,一种叫继承,一种叫实现啊我这呢,就用咱们之前一天讲课的时候呢,这种匿名的方式来造了啊呃,匿名的方式来造的话呢,我直接就new一个thread,先用一个继承的方式。哎,第二我去做一个start,然后在这个位置整一个大括号,哎,重写一下这个run方法,哎,这个大家应该都能看懂,嗯,在这里边我做什么事呢?我在咱们这个may这块呢,我先造两个变量啊,这两个变量呢,咱们这个讲完线程以后呢,再讲就开始讲这个常用类了,常用类里边呢,我们要说一个叫string buffer。
04:10
哎,你可以理解成就是一个嗯,挺特别的一个字符串了啊,这个暂时呢,咱们也就先这样来说,这呢叫S1,然后你有一个叫string buffer。啊,这样子,这呢造了一个对象ctrl alt项,再来一个啊这就写好了,写好以后,然后呢,我们在这个run方法当中,咱们呢,诶我先写一个sized,这呢叫同步监视器,我就写个S1,哎相当于让它呢来当这个锁,哎在这里边我们做点事儿,嗯,这个I1呢,咱们调这个end的方法,我里边呢让它添加一个字符叫A。哎,这个呢,大家可以理解为,就像咱们字符串呢,这个我对连接了一个A一样,跟它类似,这呢是加了一个A,我让这个S2呢,去加上一个一吧。
05:03
哎,我们错开啊,这个是abcd,这个是1234这样,嗯,在这个基础之上,我们再来一个这个S,就这时候这个锁呢,它是一个嵌套关系的啊,这呢,我让这个S2去充当。再进去的话呢,我这个一呢,我去openend写一个B。这个S2呢,看我写一个二。这么着,那当你这块呢,都添加完的时候,嗯,咱们在这个里边吧,我去输出一下这个S1输出一下这个二,这是我们这个代码。这个能看懂吧,嗯,也就是说呢,如果我们要是创建这个,呃,这个这个多个线程的时候呢,进来的时候呢,比如说这个线程它要是握住这个锁了,他呢能把这个事做好,做好以后的话呢,他要想执行到里边呢,他还得再握一个锁,叫做S2,那他才能够把这个代码呢也执行。是这个样子的啊,这呢我们有两把锁,S1和S2,这呢是咱们创建的第一个线程,那我呢,再来一个线程,再来一个线程呢,我们,嗯,用另外一种方式。
06:10
叫做实现random接口,那我还是想给他写成一个匿名的,大家看怎么写啊。这个先打先先给大家建好,那么匿名咱们这时候呢,让他去实现re接口,我是不是得在这里边写了。咱们实现的时候呢,在这里边要传一个实现renoable接口的类的对象,是不是得在这啊对,我在这呢,我就去new一个叫reable啊,回车一下啊,都给我们写好了。就是整个呢,这不相当于咱们提供了一个randomable实现类的对象,只不过是个匿名的传进来,在这里边呢,咱们去写这个逻辑了,写这个逻辑呢,跟上边儿这个类似。所以呢,我就从这儿。到这儿我CTRL一下。
07:00
拿过来,但是呢不太一样,只是说类似啊,我呢这个线程我先去握S2这个锁,然后呢,里边呢,我这个叫C这个呢叫三,然后接着呢,我这个线程呢,再去握S1这个锁。这儿呢,我叫D,这个呢叫个四。就是它这俩线程的反着,这个线程呢是先拿锁一,再拿锁二,这个呢是先拿锁二后拿锁一。嗯,成,那这个呢,咱们就写完了啊,写完以后呢,目前咱们先跑一下。你看呢,诶执行结束了。那么这个呢,你一看说明呢,它先执行的是不是上边这个线程了,哎,先这个让S1呢,呃一个A,然后呢又加个B,然后在这你输出呢就是AB,然后这个呢,输出呢就是一二,然后接着呢,后边这个线程呢,后执行的,它执行的时候呢,就接着你上面这AB呢,后边又添了个CD,哎,这面又添了个三四。
08:01
这呢只是咱们输出的一种情况啊。这个在笔试的时候呢,有时候会出这样的问题,就比如说我把这个代码写出来以后,下边呢,我问说呢,我要是去运行上述程序,诶会有哪些结果是满足的,那其中呢,就是这种是满足的。那除了这种,还有可能出现其他的结果不?是不是也有可能我下边这个线程先执行的呀。下边先执行是不是就先出来一个C,再出来个D啊,然后这先出来个三,后出来个四,接着呢又跑上面那就成了c dab了啊,这就是3412对吧。哎,这是有可能的啊,这也是对的,那么下面呢,咱们要演示的叫死锁,这个呢,代码其实是有可能出现死锁的,只不过呢,这种概率呢小一些。咱们给它加点东西,让它呢,这个概率高一些,就是sleep。Sleep,大家看啊,这个线程我们在让它握住索依的时候呢,做了一个openend的添加之后,我在这个位置呢,我们让当前这个线程啊,TH点我sleep一下,我写上,比如100毫秒啊,Out enter,给大家去处理一下这个异常,同样的把这个代码CTRLC放在咱们的这个位置。
09:25
嗯,那么此时呢,我说这个死索出现的概率呢,就大大的增加了,我们上面这个线程的话呢,它握住这个S1这个同步监视器以后呢,他把这个S1S2呢,分别去openend了一下,然后接下来呢,是不是就阻塞了,在他阻塞的这个0.1秒的这个时间之内,下边这个线程是不是太有可能执行了。嗯,他这一执行呢,是不是他拿着S2呢,就开始也sleep了,当他俩都醒的时候呢,上边这个线程等着去拿S2,下边这个线程拿着S2,是不是等着S1了,大家就互相僵持下去了,所以此时呢,我们执行。
10:03
你看就会出现这样的情况,哎,这个时候呢,既不会抛异常程序也不会终止。这个显然是不对的,咱们说这个线程,它的最终目的只有一个,就是死亡。但现在呢,他也死不了,他也执行不了是吧,这个呢就是思索,哎,是咱们需要避免的啊,这呢,我就相当于举了一个例子来把这个事儿呢,我们呃,先说一下第一个关于这个思索的理解。理解就刚才咱们这个PPT里边写的这个事儿,就是呢,不同的线程分别占用这个对方需要的同步资源,不放弃,哎,都在等待对方呢,先放弃自己需要的这个土木资源啊,下边呢,就是典型的一个例子啊,这是他的一个理解,然后呢,呃,具体的这个说明啊,说明呢就提到了,说他们也不会抛一场,只是说呢,没办法继续执行下去啊,这是第一个事情。
11:01
哎,第一个事情,然后第二个事情呢,就是大家呢,哎,我们写程序的时候呢,要避免词索啊,我们使用同步时啊,要避免出现死锁,就像前面我们讲循环的时候呢,大家不要写死循环一样啊,这个呢都是让大家避免的,下面呢不用练太多啊。啊,越练越熟是吧?啊越熟越错啊,啊这呢是一个思索的问题了,啊,其实呢,就算是说清楚了,然后这个课后在咱们这一章当中呢,我又提供了一个例子叫deadlock,哎,CTRLC粘过来。粘到咱们这个啊,CTRLV一下,诶这个打开打开以后的话呢,嗯,这呢也是一个思索的问题。哎,死锁的一个演示。啊,这个时间关系,咱们就一起呢来看一下,大家下来呢,也可以分析分析,你看他怎么就思索了。
12:02
哎,咱们就思索了好,这儿呢,整体来看,这是一个class a,这是一个class b没了,然后呢,下面呢是一个dialo,这是一个主类了,在这个主类里边,我们这呢是有一个A对象,有个B对象,两个属性,行,然后往下走,往下走的话呢,嗯,咱们这呢让他去实现runno接口了,那自然而然呢,你就重写这个run方法,这呢是里边定义的一个普通的叫in的方法。在咱们这个may方法当中,类方法当中呢,我们先造当前类的一个对象,呃,这个对象呢,我们用了个thread,把第一放进去调start,这相当于起了一个分线程,哎,这了个分线程,这个分线程呢,执行的逻辑呢,就是我们这个转方法,这是我们这个分线程,然后在下边呢,有一个啊,这不是DDL啊,这个DL呢,我调了int。这是不是还是主线程啊,对主线程呢,我们让他去调这个叫in的方法,相当于此时呢,哎,其实也算是有两个线程了,一个是咱们这个主线程,一个是我们这个分线程。
13:06
好,这两个线程呢,分别呢去操作。分别操作,咱们就先看一个主线程吧。主线程看看他做什么事了啊,嗯,主线程这个in呢,它进来,它设置了一下当前线程叫主线程,好,然后在主线程当中,咱们调A对象的foo方法。A对象的fo方法,那就掉这个了。嗯,调了fo方法以后啊,就没有了啊,这就是一个普通的输出语句了,行,那咱们就看这个事儿啊,嗯,A调fo这个方法,这个方法呢,你注意它是一个同步方法,这是我们的主线程啊,主线程呢,相当于它调这个方法的时候呢,需要拿一个锁,这个锁呢,是不是就是我们这个A的这个对象。同步方法,我们说这个对象呢是哎同步锁呢,不就是Z次吗?那这个Z次的话,不就是我们这个A的对象,其实恰好是不是就我们这里边这个A啊。
14:08
诶,他拿的这个锁呢,就是A同步健身器。然后他拿着以后拿住就是主线上啊这个假设呢,他握住这个锁了,然后他进去,进去以后呢,就做了一个输出先,然后接下来呢,他给sleep了。他给sleep了,Sleep以后像这些逻辑呢,代码比较多,它会影响你整个来看这个结构啊嗯,Sleep以后呢,接下来又输出个这个话,在这个时候呢,又掉了一个这个方法。啊,在这里边呢,我们调了一下b.last你看咱们这块我写的时候呢,是把这个B传进来的啊,就是这个代码呢,自始至终就只有两个对象啊,就是这两个AB对象啊,把这个小B传进来的,那么我们在调fo这个方法的时候呢,这又掉了个b.last b.last。在这儿呢?
15:01
嗯,这样啊,贝点尔大在这儿呢,相当于咱们这个主线程呢,它得先握住这一个同步监视器,然后呢,要想整个这个方法能够执行完,他得能调这个方法,要调这个方法的话呢,他是不是又得去握一下这个这把锁是吧。这个我们说这个,呃,同步监视器啊,就是咱们这个。这个B这个对象。主线程得先握A后握B啊,这样的话呢,我们整个这个方法才能结束,你才能够接着去释放这个B和这个A,这就是我们的这个主线程啊,然后呢,我们再来看这个叫分线程。这个呢,Start一下就掉,这个run,这是分线程,分线程一进来啊,输出了一下,输出就输出,然后接下来呢,叫b.bar b点八把这个A传进来,然后我们就调了一下这个B类当中的这个方法,这个方法要一调,那还是这个呢,叫同步监视器,是咱们这个哎B类的对象,这时候呢,唯一的就这个小B。
16:02
分线程呢,得先握住这个小B这样的一个同步监视器,进去以后呢,输出啊,Sleep一下输出一下,接着呢,它还得调这个方法才行,这呢叫a.last那是唯一的我们自制中创建的这个A类的对象小A,小A调last呢就调到这了。啊,那此时的话呢,跟上面的一样啊,这个我们叫同步监视器啊,哎,同步监视器就我们这个A的这个对象呢,小A,那下边这个呢,也是一样,还是这个小A。你看正好他俩就反过来了。这个分线程呢,得先拿着这个B的这个对象同步监视器,然后呢,要想能够结束这个方法,他还得再握住一个A这个对象作为同步监视器。他俩正好反过来。啊,主线程先拿A后拿B,分线程先拿B后拿A,他们在各自的这个,呃,拿的过程当中,中间呢,还sleep,其实就跟咱们刚才写的。
17:02
嗯,我写哪写这儿了是吧,是类似的啊,这在拿着两个同步监视器的过程当中呢,它sleep过啊,这就很容易的让另外一个线程呢,在你阻塞的过程当中去做那个事儿,所以他俩呢就反过来了,我们执行,哎大家呢,你会看到在这呢,执行到一半的时候呢,这块就终止不了了。哎,这就出现这个死锁问题。啊,就是这个呢,看着就会稍微的啊隐蔽一些。所以说死锁呢,并不是说像大家来说的啊,就我们一看啊,这个一看就是死锁,我肯定不会写这样代码,很多时候呢,我们开发当中啊,用的一些方法,后边呢,咱们像讲集合呀,讲到这个string buffer啊等等,它里边的那些方法呢,都是同步的,只要你用同步的方法。哎,你就得知道它会涉及到同步监视器,如果你用的是两个不同类的同步方法了,那它就是两个不同类的对象,作为同步健视器,你要这个呢,线程是先调A后调B,那个是先调B后调A,里边就一定会出现死锁的问题。
18:07
啊,所以你要小心一点啊,小心一点,那么怎么去避免呢。哎,一个呢,是考虑专门的算法,你避开,让他呢,这个先调A后调B,那个先调B后调A,避开这样的问题,另外呢,就是尽量少的去使用同步资源。有些时候呢,不用同步,你就不要同步了。啊,没必要去同步,就不要使用同步,你使用多了不管效率低,而且呢,呃,一嵌套厕所了,哎,再一个呢,就是尽可能少的去嵌套啊,只要一嵌套就会有风险。哎,这呢,就是大家呢,尽可能哎想一些办法去规避这个死锁问题啊。嗯,还是那个意思啊,就是死锁的话呢,不是说呢,你这个程序运行的时候呢,运行出结果了啊,说这就没有死锁,有的时候呢是有死锁,只是呢,你你没有发现。嗯,你像咱们这个里边,我要是把这个sleep这注释一下,把这个呢sleep注释一下。
19:07
我们再来执行。啊,这个时候还还给锁住了是吧。嗯,这个你看它还是没有执行啊,嗯,就是呃,当然这个我们把它关掉呢,只是说让它能够执行的,这个呃,不出现思索概率呢,能稍微大一点啊,那人家没好使。啊,这个还是没好使是吧,没好使就没好使吧,就是说我们不是说呢,你这块一执行啊,没出现思索,说你这个程序就没思索。不一定是吧,你看你里边呢,这个呃,锁之间有没有出现这种嵌套啊,有嵌套的时候呢,又是多个线程,那就很容易出现死锁了,咱们家sleep的意义呢,也只是让这个死锁的概率变大而已,哎,并不是说呢,原来你没有啊。行,这个关于思索的问题呢,我们就说到这儿。
我来说两句