00:00
好,咱们继续看下边这个内容,然后呢,关于整个我们县城这一章啊,诶后边这块呢,还涉及到一个内容叫县城的通信。啊一提到呢,叫通信呢,感觉说呢,哎呀这块好像挺复杂的是吧?诶其实的话呢,不然这块呢,其实就是几个方法的调用而已,啊基于我们讲的同步机制,然后呢,我们讲的一个通信的通信的内容,诶首先的话呢,我们看一看怎么叫线程的通信。啊,说为什么要处理县城,县的通信这块,你看有一段话说呢,当我们需要多个县城来共同的完成一件事物。啊,或者一件任务。那其实这块呢,共同的其实呢,就涉及到了可能会有线上的安全问题了。并且呢,我们希望他们呢,有规律的执行。那么多线程之间需要一些通信的机制,可以呢协调他们的工作,以此呢,实现多线程共同操作一份数据。所以这块这个线程通信啊,实际上呢,也是基于他们共同来操作一份数据,也就是说呢,需要呢,在同步的基础之上呢,来解决这个通信的问题了。
01:04
比如下面有个例子,状态线程A呢用来生产包子啊,线程B呢是用来吃包子,其实这块这个包子这块呢,就是一个共享数据了。啊,然后呢,线程A线程B呢,他们是协同来处理的一个生产一个消费,然后线程这个B是吧,必须等待线程A呢完成以后呢,才能够执行。啊,就是B呢,你要想吃A呢,得先生产出来,然后这块你才能吃,甚至说呢,在这个基础上呢,我们还可以再限制,比如说A呢,生产包子也不能无限制的生产,甚产到一定数量的时候呢,这个如果还没有消费掉,这时候你得停一停啊停一停的时候呢,这个县城币呢,可以取来吃了。哎,他只要吃,比如说你上线的话呢,比如说1000个包子,只要让他吃一个。啊,变成这个999个了,你就可以继续生产了。然后呢,这块吃啊吃完了,吃完的时候呢,这块你不能再吃了,已经没有了,这块呢,只要生产一个你就可以继续吃,这呢其实就体现的就是一种通信的机制,那这个通信机制呢,我们使用的是用等待唤醒机制呢来体现的。
02:05
那就是我们下边要讲的这样一个场景,那这里所谓的等待唤醒,实际上呢,就是对应我们相应的方法了,等待就是wait。可以呢,是无限制的wait,也可以呢,是有等待时间的weight了,然后呢,这个唤醒呢,就是我们对应的叫notify。哎,Notify呢,就唤醒一个,然后notify all呢,就是把所有的weight的线上呢都给唤醒了。啊,其实呢,我们这儿呢,讲叫现成的同步机制,其实对应的就是这样的几个方法的使用而已。诶就拉过来了啊好,所以这块呢,首先关于为什么要处理现城的通讯问题啊,这块呢,首先有这样的一段话,就是为了呢,他们能够协同的去处理相关的这个场景啊实现呢,就是这个县城之间呢,他们操作化呢,呃,有规律的去做这个执行。好,那么这块我们首先呢,可以通过一个案例呢,来做一个说明,就是这里边儿的这道问题说呢,两个线程打印一到100。共同来打印一到100,然后线程一和线程二呢叫交替打印。
03:01
一个打印一啊,另外一个就打印二啊,那个打印一个三啊,这个打印个四,就这样交替打印的这样一个场景。好,这呢,我们看一下该如何呢去实现,这呢我们开始去写一下。不妨呢,我们就叫做print的一个测试了。啊,这个呢,是我们这个案例。好转过来啊。嗯,这块的话呢,同样呢,我们需要考虑呢,使用继承或者是实现的方式呢,我们去写了,那不妨这块我们就用实现的方式来写吧。哎,Class一下这个,比如说就叫做print,让他俩去implement一下这个啊。这样好,然后CTRLL把这个run方法呢做一个重写。嗯,这里边儿呢,需要注意呢,我们打印的是一到100,是大家共同打印一到100,不是说每个县上都一到100了。所以这块呢,其实涉及到有一个共享的数据。其实这呢,也类似于咱们卖票嘛。啊,只不过这个时候这个票号呢,就是从一到100啊,所以呢,我们提前呢,把这个变量呢,得声明到这个位置啊,体现呢,比如说对外边呢,就不访问了,直接private啊,Int类型的咱们叫number。
04:06
那这个一开始这个值呢,就是一了。那么在这里边儿呢,这个也写一个的一个。看在这里边去判断哈,如果呢这个,那么呢是小于等于100的。这呢我们在里边呢,就去打印它这个先输出一下啊thread.current th.name。哎,冒号一下来加上一下我们这个number。啊,这个打印完以后呢,他再做一个加加的操作,哎这样啊,这呢来一个else,这来一个break是吧?好这个呢,就是我们说的这样一个打印数据的一个行为了,啊这块我们就可以呢,写一个main方法。哎,创建当前这个类的一个实例。好,然后呢,我们去这个thread。哎,把这个。信的放进去,这个怪怪的哈。啊,先呢,给他声明一个对象啊。
05:03
嗯,其实这块的话呢,我们还可以用它另外一个构造器哈,当然我们讲这个TH的时候呢,CTRLP一下,一方面呢,我们把这个实例呢放进去,另外呢,后边直接就可以起个名了,是吧。这个比如我们叫呃,线程一。哎,这是它了,嗯,这块呢,我们把它呢,哎赋一个值。这样啊,然后呢,CTRLD一下再复制一份。检查二。哎,t1.start一下。哎,复制一份。好,那么这呢,这两个线呢,就开始呢,去执行我们上面这个run方法了,那执行的时候呢,关于这个number的操作呢,我们说得需要考虑。同步是吧,啊要不呢,会有这个现的现成的一个安全问题,比如说我们在这个位置吧。我们加一个啊叫thread点来一个100毫秒。这呢,我们来一个catch。哎,先这样写来,我们先去做一个R。
06:01
好,首先呢,我们来看一看是不是有这个线上安全问题。嗯,你看出现了。对,出现了啊,就这块儿,我们是小于等于100的时候呢,他才去打印,那你这101呢,怎么还打印了呢,显然这不合适。那解决这个限量安全问题呢,我们考虑呢,使用比如说同步机制,同步机制的话呢,咱们就用同步代码块吧,哎,我们考虑呢,就把这段代码呢,给它包起来,CTRL2的加一个T。然后呢,找这个袋子的啊,这个位置写。是这次可以啊。对啊,因为当年的这个this呢,表示的就是print的实力,那就是我们唯一的这个P啊,没问题,好再去做一个run。啊,这个我们停留这个时间稍微有点长啊,这打印的就会有点慢了。啊,似乎呢,这块你发现它打印的都是线程一是吧。啊,都是限量一啊,这个呢,并不是因为咱们包错了,咱们并没有把这个while呢给它包进去。啊,这个咱们昨天呢,其实也提到过这样的场景,就是大家呢,在处理这个共享数据的时候呢,这个我们不能包少了,你包少的话呢,这就照样不安全了,你要包多的话呢,能是安全,但是你包多的话呢,相对应呢,你这个串行的这个场景呢,是不是就更多了。
07:15
那就不太合适了啊,所以这块呢,我们包这个共享数据的代码的时候呢,既不能包多了啊,也不能包少了这个注意。好,那么这块呢,虽然打印的都是线程一,其实我们这块没啥问题啊,咱们就紧接着它呢,接着来考虑第二问题了,说呢,我们现在想要两个线程交替打印。诶交替打印啊,交替怎么交替呢?其实呢,是这样个想法,比如呢,第一个线程进来以后,他判断完这个满足了,他就给打印了,他打印完以后呢。咱们呢,在这个题目当中,他呢,就出来这个大括号了,出来这个大括号以后呢,下一刻他又给拿到了。啊,就成这样场景,咱们现在希望呢,呃,你下一刻呢,不要拿到了,说白了就是你要进入一个阻塞的状态,让另外一个线程呢,直接过来拿。
08:03
那怎么就阻塞了呢?对,这块我们就使用一个方法,这个方法呢叫做wait。啊叫wait就相当于呢,你这块呢,先打印了你当前这个一了,你打印完一之后呢,加加了,加加之后呢,你就阻塞一下进入等待状态啊这个胃的方法呢,它本身又有异常啊,我们这块又再来一个拆开。所以这块呢,就是啊,线程一旦啊执行此方法就。进入啊,这个叫等待状态。这个等待状呢,其实就相当于是一种阻塞了。这就走不动了啊好,那么他一等待,注意它还有一个特点,那么会。啊,同时是吧。会释放对同步监视器的调用。这个呢是非常重要的一个点,它跟这个不一样呢,咱们说了,你拿着这个同步监视器进去了,然后给睡着了。咱们昨天也形象点去,说你在厕所里边,一进去厕所呢,把这个门咔一锁,现在你就握着这个锁了,然后在里边呢,一不小心睡着了。
09:06
你睡着了也不会说这个门自动啪就开了是吧,这还是不安全的啊,诶你睡着了,这个门也是锁着的,所以呢,它不会释放。同步加热器。注意这块呢,是一个也算比较高频的笔试题,就是问,哎,这个sleep和我们这个wait呢,有什么区别啊。你看我现在就说了一个区别,它呢不会释放对同步加湿器的调用,而它呢,会释放。OK,那么它一释放的话呢,那另外一个线程不就进来了吗。那另外一个先生一进来之后呢,他就也进去了啊睡一下呢也行,反正他睡的话呢,他也不会释放这个同步天然气醒了,醒了以后呢,接着呢就打印了,打印完之后呢,他也给睡了。啊,那相当于目前呢,就俩县城都睡了,所以这时候我们要是乱的话呢,就只能这么着了。然后呢,就阻塞了是吧,走不动了啊行,那么就各自呢,只打印了一个,那不行。
10:01
那你得醒啊。是吧,那这个醒的话呢,咱们就不用这个wait啊,CTRLP呢,它还有这个带时间的哈,达到这个时间之后呢,它就自动醒了,咱就别用这个了,咱们想让交互啊。呃,那么我们就得唤醒,唤醒呢,就意味着我们第一个线程。第一个线程这块,你为以后呢,第二个线程进来,第二线程你应该在你第二个线程wait之前呢,把第一个线程给唤醒是吧。就是把他叫醒,就相当于这个有的工作呢,就是两班倒嘛,呃,第一个人呢,这块呢,工作工作完以后呢,他就哎回去睡觉了,然后第二个工人过来,第二个工人工在这块呢,就工作的以后呢,这个醒了以后,诶怎么着?迷糊了是吧,就是两个人互相叫是吧,对第一个工人这块呢,比如说工作完以后啊,回去睡觉了,然后这个。第二个工人呢,过来工作,工作完以后呢,他得回去把第一个叫醒,第一个过来工作,然后他再回去睡是吧。差点绕迷糊啊。哎,回过来,那这呢就意味着我们第二个线程,你在喂之前呢,你得把第一个叫醒。
11:04
叫醒呢?我们写哪儿呢?你说写这儿是吧,啊,其实这个我们可以写到开头也行。啊,开头这块,比如我们几个叫naughty five是吧,哎这呢,因为就俩嘛。写这了,写这也很也也行,或者我写这儿是不是也行。哎,怎么理解啊,你看这个第一个线程呢,你说我们第一开始一进来上来着赛,这时候呢,也没有被wait的线程,所以就当没执行一样,你就接着往后走,第一个线程呢,为了把一给打印了,然后第二个线程呢,哎,就是你第一个线程这块还释放同步显然器了啊,你一释放呢,我们第二个线程就能握这个锁,它一握以后呢,他一进来它就notify。那就意味着他把这个线程一呢给叫醒了。虽然叫醒了,但是没有用。因为你这时候也没有同步资源是吧,我这限二呢我还握着呢,所以呢,诶这时候我们限制二呢还继续的执行,即使呢他sleep了,这个呢,人家也握着锁呢啊接着再往下走,然后呢,他就把这个二呢给打印了,打印完以后呢,这时候他线程二呢wait了,还一位呢一释放锁,然后线程一呢本来不是醒了吗。
12:09
醒了之后呢,注意不是从头开始啊,从你被wait的这个位置呢,继续往后执行,你要后边有代码的话呢,还得执行。咱们这没有了,没有了你就回去,回去这块你就,哎,因为呢,第二个线程不是已经释放锁了嘛,所以你就能进来了,哎,你一进来的话,你把二呢给他叫醒。哎,就这样好,我们做一个软。哎,这时候你看线程一线程二,它就有这样的一个交互。这个咱们让他睡的时间稍微有点长了啊。比较慢一些好,那正常,这不就结束了吗?那这个呢,就是我们看到这种交互的一个场景。啊,这呢,就是我们要讲的这个线程间的一个通信啊,那回过来这块我们提到了有这样的几个方法的一个调用,诶对应的一个叫wait啊,一个呢叫notify。哎,那其实呢,还有呢,叫notty y o是吧?OK来我们说一下这三个方法来使用,那这个位的话呢,刚才提到了,说一旦呢,线程执行此方法就进入等待状态,同时呢,会释放对同步监视器的一个调用。
13:15
啊,这个大家注意一下。啊,那么这个被wait的线程要想唤醒呢?要想继续执行呢?我们需要呢,调用下边这个叫notify。那这个呢,我们说呢,一旦执行此方法。哎,那么就会啊,叫唤醒被wait的。啊,县城中,哎,我们叫优先级最高的那个。注意这个呢,Notify呢,它只能唤醒一个啊。那一个。啊,这个县城。那有同学会想说,那我万一呢被V呢,有三个线程,因为你一样怎么办呢?那就随机的唤醒一个了。啊,这块说一下啊,说如果哎被。
14:02
哎,被wait的。多个线程的优先级相同。则随机唤醒一个。啊,这个应该能看懂啊,所以这个notify的话呢,就是唤醒线程的意思,那被唤醒这个线程呢,从你被的那个位置继续执行啊。啊,这我们再补充一句,被唤醒的这个线程,然后呢,从当初被的位置。啊,继续执行。这个呢叫notify,然后notify all呢,就是说啊,一旦执行此方法,就会唤醒所有被wait的线程啊。这个呢就比较好理解了,那那你涉及到多个线程的时候呢,我们哎考虑呢,去调这个方法。行,这呢,就我们说的这样的三个方法,这三个方法呢,相互之间调用,那就能够构成一个通信。
15:07
哎,这样的一个场景。好,那么针对这三个方法的使用啊,这块我们得多说两句了。哎,多说两句啊,首先回过来。嗯,这块的话呢,我们会发现哈,这个notify,这个wait啊,你看我是不是也没有写,谁来调他们呀。没有血谁掉?那你说是谁掉的?对,凡是在方法当中,我们没有写谁来调的,如果是非静态方法,那就是少了这次了静态方法,那就是当前类是吧。好,那这块呢,显然是一个非静态方法,那就意味着我这块呢,其实省略了C4点。好,省略的这点。这呢,我就显示的加上了一运行肯定没问题啊,我就不运行了,然后呢这块呢,呃,我们需要注意个事儿,咱们现在的话呢,把这几个方法你看我其实呢都啊不自觉呢,都写在这个同步代码块里了。
16:02
那其实这时候我们有一个潜在的要求,那就是说这个三个方法它的使用呢,必须是使用在我们的同步代码块,或者是同步方法当中的。啊,这个我们先写一下啊。说。此三个方法的使用必须是在。同步代码块或同步方法中。哎,注意我现在说的是同步代码块和同步方法,说的呢就是synchronized,那么问一下能够使用在lock当中吗?那咱不是上面讲的这个,诶锁是吧。注意是不能的哈。啊,那么我们在lock这种当中呢,这个要想实现这个通信怎么办呢?这个呢,咱们现在就不讲了啊,这个我们就先是写一个叫叫超纲的内容了。哎,怎么着呢,就是这个lock呢,需要呢配合啊,以后呢,要讲那个叫condition啊,去实现叫线程间的通信。
17:02
而且呢,这种操作方式呢,会更灵活一些啊,咱们现在就不多去讲这个事儿了,基础阶段咱们讲的过多了,那就超出了基础的这个范围了啊。OK,那现在的这三个方法只能是在S的啊这个结构当中去使用。啊,那这个为什么得得都得在这儿用呢?其实还有一个体现,大家你看这块,我们此时的这个同步显然器啊,就是Z。似乎跟他一样是吧。你看我现在我换一个同步显然器。换一个,那我们就自己造一个吧,比如说声明是object。这儿呢,我就造了一个object,那在当前这个实现里边呢,它就这一个,我们就造了一个实现类的对象,所以呢,它是唯一的。然后呢,我把这个OB呢,放在这个位置,其实也是线上安全的。诶,我把它呢改成叫OB接了哈,行,这个呢仍然是前安全的对吧。
18:02
因为他是唯一的。但是呢,我们去运行的话呢,它就报错了。你看确实报错了这个错误的信息呢。这个我们叫他,叫他。非法的monitor state exception。啊。这个是什么意思呢?哎,主要原因指的就是当前我们使用的这个同步监视器。这个同步器呢,跟我们notify和方法的调用者不一致了。哎,导致的,所以这块呢,我们回过去呢,需要补下一个知识点。什么知识点呢?就是此三个方法的调用者。哎,必须是。哎,同步代码块或者是同步方法的。呃,当中的这个叫同步监视器是吧。哎,同步加湿器。啊,那么通过这个角度呢,我们也能够看得到,它必须得在同步代码块或者是同步方法中去使用啊,因为只有呢,这两个结构当中才存在着同步监视器,咱们在这个lock里边也没有同步监视器啊。
19:09
哎,这块要注意,必须呢是同步监视器,否则会爆。这个异常。啊,这呢,就是我们说的这个场景,那要想不暴力异常,在我们这个问题当中,我们是写的obj这个位置呢,你就必须呢用obj呢去调。啊,在这啊。诶这块呢,大家要小心一点。那么我改成obj以后呢,我们再去run。哎,这就没问题了,这个有点慢啊,咱们把这个数呢,给它改小一点,改成20。哎,再去转。哎,这样一个情况。哎,这块你看它就不会出现这个问题了。行这块注意一下啊,就是这个方法的调用者呢,都得是我们这个同步监然器好了,那么这个同步监然器呢,当初我们讲这个同步代码块的时候提到过,说任何一个类的对象都可以充当同步器。
20:01
那么就意味着任何一个对象都应该有能力调这个方法。任何一个对象都应该有能力调离方法,那么你说这个方法定义在谁里边?对,应该定义在object当中,所以我们按照CTRL键1.object里边,这不就声明了一个方法了,所以呢,当初我们在讲object的时候呢,说提到过几波方法是吧,那其中呢,有几个方法呢,就涉及到notify notify all,还有wait是吧?这呢,我们涉及到这几个方法呢,当初我们讲说后边呢,我们讲到线程的时候,说到线程通信,我们再讲这几个方法指的呢,就是这个事儿。诶,所以回过来,那这块呢,第三个点涉及到呢,说此。哎,三个方法,哎,声明在。哎,Object这个类当中。啊,这呢,我们就知道为什么会在这个类当中了,因为呢,任何一个对象呢,都有可能会去调这个方法。因为你这呢,同步监视器呢,可能是任何对象,你不能这块呢,我这写的同步监视器,随便整个对象,结果这块一调方法没有,那不尴尬了吗?他还得要求呢,他俩这个同步监视器就得是它的调用者了,是吧?所以呢,必须每个对象都得有这个方法,所以只能是写在object当中。
21:11
OK啊行,这个呢就说清楚了。啊,说清楚了啊好,这个说清楚以后呢,其实这里边儿我就隐含着了,呃,相关的这个wait和sleep的一些区别。啊,这样一些区别,那么这个区别呢,看能写不。你看这两个呢,长得不太一样。咱们前面说过呢,一类名人体呢,就长得一样的。那有长得不一样的,还让你比较区别,有可能还真的有点相同点是吧?那你说这哥俩的相同点是什么呢?对,就是一旦执行的话呢,都会使得当年线程进入一种阻塞状态是吧。啊,一旦执行。啊,当前线程。当前县城。
22:00
都会啊进入。哎,这个阻塞的这种状态是吧,啊嗯,这呢算是一个相同点啦,然后呢,这个不同点呢更多一些。那想想都有哪些不同点呢?一点点来啊,我我这块提示一下啊,首先生命的位置。是吧?哎,喂的方法呢。声明在。是不是object那种。哎,刚才咱们不是刚说了吗。然后呢?哎,他呢,是哎,声名在surprise的。这个内容。哎,这是它是吧,而且呢,对这个sleep方法呢,是不是还是个静态方法。啊,这呢算一个区别了,只要生命的位置不同,好,然后的话呢。诶,我再写一个叫使用的场景不太一样。什么意思呢,你看这个呢,刚才我说了它得在哪用。
23:03
对,它呢是只能使用在。在这个同步代码块或同步方法中。啊,那这个sleep有使用的限制不?对,咱们昨天不是还写了一个十九八七零五四三二一,然后新年快乐嘛。是吧,其实呢,你在任何你想用的位置呢,是不是都可以写是吧。啊,只要你想用,说我现在想让当然先生睡一会儿,你就可以用。啊,随便都可以用,所以说呢,你看我这儿还放了一个。这个常用的方法。这事上有一个比较奇葩的一个是吧。啊,看源码的时候呢,这个虽然有点不清楚啊,但这个事儿呢,确实存在过啊。这个呢,诶调方法的时候呢,里边呢,就让这个县城睡了会儿啊说这块呢,以后我们方便的让客户给钱再去做一些优化是吧。嗯,对。OK啊,行,任何位置你想用sleep呢都可以啊。
24:02
说呢可以在啊任何啊需要使用的这个场景。好,所以呢,这块呢,它没有这个具体的限制的啊,好,那么接着。接着的话呢,诶我们考虑虽然说呢,这个sleep呢没有限制,那如果说呢,我们让他俩呢,都使用在同步代码块或者同步方法中的时候呢,他们这块呢,还有一个区别。就是释不释放锁的问题是吧?哎,那么这块呢,就是诶使用在同步代码块或。同步方法中啊,那么这块呢,涉及到它呢。对它呢是会释放,就一旦执行是吧。会释放这个同步加然器。嗯,对,这个是不一样的,而sleep呢。不会。啊,一旦执行。诶,不会释放这个同步器。
25:03
诶,这呢,看还有个区别。好,这呢有一点区别了,这块呢,我们其实还可以再写一点区别。对啊,再一点区别什么呢?它俩的共同点呢,是都会进入阻塞状态,那阻塞呢,一定不是最终状态,那下个问题呢,就是结束阻塞的方式方法不一样是吧。嗯,结束阻塞的这个方式啊,那么呢。对这块呢,其实它有好几个方法。啊,我这个CTRL f12一下这个呢,它有这种不带参数的,也有这种带参数的,带参数呢,就是达到这个时间以后呢,它就自动的被。唤醒了是吧?哎,当然我们这个sleep呢,它就只有一个方法。是不是只有那个带时间的了。诶,所以说这块为什么我们怎么写呢,它的结入组的方式呢,是诶这个。叫什么两种情况了哈,说到达。
26:01
到达这个指定时间。自动。结束阻塞是吧,哎,或。哎,通过被notify是吧。唤醒。哎,然后呢,就要结束阻塞。呃,这个组织呢,咱们就泛泛的去说了。其实呢,我们要按照这5.0及之后的这个生命周期来讲,Wait呢,刚才呢,我这块说的其实是两种状态了,这种就是在等待时间的人,叫time的waiting是吧。这个呢?叫waiting了。对啊好,然后呢,这个呢,它就相当于是叫time waiting呗。对,应该能明白啊,这个呢,就是哎,到达指定时间的时候。啊,自动的结束阻塞,哎,我就用的是它了。现在算是也有点儿区别了。基本上的话呢,也就这样了。啊,也就这样了,好体会一下啊,他俩的这个区别。
27:00
这呢也算是一道比较常见的这个笔试面试题啊,因为呢线上呢,在实际上我们笔试面试当中啊,其实问的还比较多。啊,那既然而然,那既然呢提到县程呢这块呢,就会提到这个阻塞的这个问题,通信的问题啊,这块儿就自然而然的就会有这样的一个面试题啊好,那这个我们就说到这儿了,然后关于我们这一章呢,其实就算是偏向于收尾了,那同学这不后边还有内容啊,后边这个呢,我们新的两种创建方式呢,算是个补充。啊,那么在讲这个内容之前呢,咱们这块呢,再讲一个案例,这个案例呢,就对咱们整个这一章当中涉及到的一些知识点啊,就都给融合在一起了,这里边儿呢,既涉及到了多线程的创建,线程的同步机制,也涉及到了线程间的通信。那咱们呢,来写一下这个案例,通常一说到多线程的这块呢,这一道问题呢,是比较经典的啊,就是生产者消费者问题。来我们在这个通信这块呢,新建一个。生产者叫做producer。
28:03
诶sir是他,诶消费者。Consumer是吧,关于他的这个测试。哎,粘到这儿。诶,这道问题的话呢,算是一个大的一个练习题了,哎,大家下来呢,把它呢也写一写,首先呢,我们来分析一下。在这儿说呢,生产者将产品交给店员,而消费者从店员处呢取走产品。店员呢,一次只能持有固定数量的产品,比如说20个,如果生产者试图生产更多的产品呢,店员呢会叫生产者停一下,如果店中有空位了,放产品了,再通知生产者继续生产,如果店中呢没有产品了,店员呢会告诉消费者呢说等一下,因为已经消费完了,诶如果店中有产品了,再通知消费者呢来取走产品。这里边儿呢,就涉及到了一个通信的一个问题。嗯,体会一下啊好,这呢我们做一个分析。
29:01
分析第一个问题说是否是多线程问题。是,那这呢自然而然,那到底都有什么线程呢。县城。不是说这个类和对象啊。是不是生产者是一个县城吧?对消费者,一个县城最起码得有俩县城。那电源还是先成吗?店员不是了,对吧,诶生产者负责生产的这个负责消费的店员呢,你可以列成他们共同操作的这个数据吧。对啊,至少得有俩县城,当然你说我有三个消费者,我有两个生产者也可以,那县城就更多了。好了。多线程问题,那涉及到多线程问题的话呢,下个点说是否有共享数据。因为呢,如果多个线程没有共享数据的话呢,你也不用考虑线程安全问题是吧,那有共享数据的话呢,就会考虑有现场安全问题了。
30:02
有吧?好,那自然而然我们会问,共享数据是谁?呃,是对,这里边儿提到这个产品。咱们当然也可以呢,理解成就是产品的这个数量是吧,一个呢,让这个数量加一个让这个数量减。共同来操作这个产品的这个数量的,OK,好,那既然有共享数据问题了,那自然而然的我们说呢,就是呃,是否有线程安全问题是吧。哎,那这块呢,显然呢,就是有啊,因为呢有共享数据。那既然呢有现场安全问题了,那下一个问题呢,就是是否需要呢考虑。处理现场安全问题,那毫无疑问是得需要的是吧?啊,是否需要。处理线程安全问题。啊,这个显然是是啊,那么下边如何处理呢?这个我们可以叫使用同步机制。好了,这是我们说的这个同步机制呢,就进来了,那在下一个问题说是否存在县城间的通信。
31:05
有通信是吧,那这个通信是怎么体现的。就是。对,你比如说我们生产的话呢,是不是生产到20的就到头了。你得喂是吧?然后呢,这个你要是消费没了的时候呢,消费者得喂他一下。那一旦呢,又多生产了一个呢?这时候你又被notify唤醒了,可以继续消费了。那这块呢,你到20的到头了,消费者只要消费一个变成19了,你可以呢,就继续的生产了,诶中间呢,有这个通信啊,这呢我们说是存在。好,那现在呢,我们这一章里边涉及到这些知识点呢,就都能够涵盖进来,那下个问题就是说如何去设计了。这呢就得使用叫面向对象的编程次项是吧?哎,我们这里边儿涉及到这种线程啊,那你都得是通过相应的类的对象呢去体现了。共享数据,共享数据的话呢,你要是单纯的比较简单,那就用个变量,复杂一点的话呢,还得设计成类。
32:01
哎,这样的去体现。怎么去设计呢?嗯,这块呢,只要涉及到多线程问题了,我们呢提到了有两种方式是吧。集成也好,实现也好,都行。啊都可以,那这儿呢,我就用。嗯,都都都可以是吧,都可以啊,我用个继承吧。继承的话呢,这时候你要注意他们有共享数据,你怎么去体现这个共享的这个概念啊。好,那么共享的数据呢,是我们的这个产品,产品的话呢,其实呢,生产者也好,消费者也好,都是交给店员来处理了,店员一会儿叫停这个,一会儿唤醒那个,那干脆呢,我们把这个产品呢,就封装在电源这块得了。所以大方向来讲的话呢,我说有这样的几个类啊,首先的话呢,有一个电源。叫clerk啊。然后呢,我们还接着呢去,哎,比如说这个叫producer。这个呢,叫生产者是吧。
33:00
然后呢,还有这个class呢,叫。很凶是吧?哎,这叫消费者。哎,生产者。这个叫,哎消费者。这个呢叫电源。行,这个呢,我们就呃,有点这个面向对象的这个意思了,下边我们看如何去设计啊,那么生产者消费者恰好呢,就是我们所说的这个县城吧。那我这块要用继承的话呢,那不妨我就直接让他去继承这个thread了。下边呢,也同样的道理是吧。好,那么生产者消费者的话呢,我们就去调他们各自的软方法,他们要做的事呢,不就是一个生产一个消费吗。所以软方案里边呢,就是生产消费。啊,这个呢,你可以呢,我们就写成叫well了,它就不停的生产呗。这块你可以写一下是吧,这个生产者。开始生产产品。诶点点点,然后下边就一顿生产是吧,这个生产的话呢,其实呢,我们可以理解成呢,就是把这个产品的数量呢,让它增加呗。
34:06
这个是增加,这个是减少,增加减少呢,我们把这个数量干脆呢,都放在电源这块来维护得了。所以在这里边儿呢,我们声明一个叫型的叫,比如叫product number。诶一开始是个零,这呢,就维护呢,叫产品的这个数量。所以呢,我们让这个生产者呢,去生产,相当于呢,就是调用我们可乐这里边的这个电量,让它呢去增加。然后呢,消费者呢,就让它减少,增加减少,我们通常都用方法来去体现。以后呢,咱们说呢,通常对这种属性的修改呢,都通过方法来做哈,所以呢,我们就去提供。叫哎增加。哎,增加产品数量的这个方法还有呢,就是哎减少。呃,减少啊。产品数量的这个方法好,那么对应的上面这个就是生产的一个行为,诶我比如我们叫I。
35:02
哎,是吧。哎,在这里边去增加的,然后在这呢,诶public VO这个呢,比如说叫减少啊,Mines是吧,减法的意思。诶转让来减少。好,那么这呢,增加这个减少主要呢,是来操作它了。然后呢,我们就让,诶这个生产者的话呢,其实呢,就去调用咱们这个clerk里边的这个方法就行。那现在问题就是说,诶这怎么调呢。诶这呢,其实也自然而然的遇到个问题,那我们现在操作的其实就是共享数据了,那目前呢,这个生产者和消费者没有体现这个所谓的共享的概念了。我们在这里边儿呢,就去声明。谁明谁啊?产品数量呢,咱们放到clockrk里了,是不是就声明这个Clark了?哎,Clark。哎,这样这样一个变量,好,那么这个变量的话呢,我们需要呢,给它赋个值,这个赋值呢,我们就可以使用构造器了。Public。
36:01
哎,丢是吧,这个呢,我们通过这个参数。哎,克拉克。这四点,Clark。诶等于这个拉RK好,通过这个扩大器的方式呢,我们给它呢,做一个初始化,初化完以后的话呢,我们在这个run方法里边,不就是调用拉呢,不断的让它去生产产品嘛。这不就可以了吗?当然你生产的时候咱们让它稍微的慢一点是吧,那这个我们就可以呢,Thread。第二,比如说sleep一下啊,每隔啊,比如说这个50毫秒来生产一个产品,这不就是这样的一个逻辑吗。你看这个OK吧。好,那么类似的这个逻辑呢,咱们就写到这个消费者这里边儿了啊,这个跟他的这个逻辑呢,是基本上是一致的啊也去声明这个clerk,这个呢是你的消费者,诶通过构造器呢,给他做一个赋值,那么我们在具体造这个线程的时候呢,我们回头呢,是不是都放同一个clerk。
37:00
这样呢,不就达到是一个共享的一个效果了吗。咱们前面已经有过这样的思路了啊,咱们讲这个同步的时候,这个课后练习题。诶,这个里边不就是这样的一个思路吗?当时呢,我们这个不同的这个consumer。哎,在创建多个线程的时候呢,我们用的是同一个count,那它呢,自然而然就是共享的。好,这呢是同样的思路。然后呢,这个消费者的话呢,他就是主要用来消费的,诶我们先把这个逻辑呢,给他CTRLC过来。说那叫消费者。诶,开始消费产品。好这块呢,也是比如50毫秒这个位置,可这个我们调另外一个方法。哎,这个。行没问题了,好,那么他俩呢,线程的执行就都跑到我们的这个可乐RK这块呢来去操作了,所以现在这个重点呢,是不是就执行这块了。行,那么这呢是一个增加产品,那按说的话呢,增加产品进来呢,就是无条件的我们让这个产品的数量呢,加加一下。
38:04
加加完以后呢,我们这块打印一下说呢,比如说谁谁谁生产了第多少个产品是吧,我们就来一个th.current get name。比如说叫生产者一是吧,叫生产了利。把它呢往这一粘。哎,各产品。产品,比如说一开始的时候这个是零,然后加价以后呢,就变成一了,说生产了第一个产品。啊,应该这样啊,那对应的这块呢,就是呃,减少了几个产品,但是这块你注意我们增加的时候呢,它有个限制是吧。对,你不能无限制的给大家去增加。我们得判断一下是吧。说呢,如果呢,你这个product这个number呢,你是大于等于20的。其实这时候呢,应该是等待是吧。啊,再说得等待,你就别生产了。那这个等待的话呢,其实我们涉及到就是得wait一下是吧。
39:01
但这块用呢,我们的大前提呢,就是这块有异常啊,我们先给大家传开一下吧。诶,但是这个V的话呢,它有个大前提,它得使用在同步当中,其实呢,我们这个product number它就共享数据了,这在就操作共享数据了,所以这块呢,是不是自然而然的就是吧。好,然后这块呢,还是用的Z,这个呢,不就Z吗。诶,这个同步间然器呢,还得是它的调用者这块呢,就是这次,所以没问题啊。如果你要大于等于20,我就wait了。那如果要是不满足这个条件呢?你就生产呗。生产完以后。那就完事了。这块你还看不到说需要唤醒的事儿是吧?嗯,这个就。就这么着了啊。这块有问题吧,看看。嗯,我写到这儿了,实际上这块这个代码自然而然的对应的就是小于20的。因为你大于等于20不就进去就wait了是吧,那这块自然而然就是小于20的啊OK行写到这儿,然后接着我们看这个逻辑啊,这个呢,诶跟我们刚才说的类似,咱们就先写,比如说你一上来呢,按说呢,就让这个产品数量就减减。
40:05
这块你写一个说哎谁谁谁是吧,thread.current th get一个name。那叫消费了利。Product number。每个产品。这块呢,其实有一个小细节哈。这俩呢?得颠倒一下。你想想。假设呢,我现在有一个产品是吧,应该是消费了第一个产品,然后这块减减变成零。你不能这块呢,先变成零了,说消费了第零个产品了。对这个小细节呢得注意,所以呢,我把它呢得提到上边啊,得这么着。但这个消费呢,刚才说了,你也不能无限制的去消费,消费了第负一个产品也不合适了,所以这块我们得依附一下是吧,这个product number呢,如果你要是。咱写不合适的吧,如果你要是小于等于零的时候呢,相当于不就没有了吗?那这时候你得等待是吧。
41:03
哎,等待呢,就是还是我们说的这个。哎,得这样了啊,然后为这块呢,我们有一场先给他哎做个串开尺。诶这么着,然后呢,呃,这个weight呢,我们说要用用得在这个ne当中去使用了,这个还是Z做同步监视器,这个呢,我省略Z了,诶它俩是一致的,没问题,跟上面逻辑一样啊,那如果呢,你这个product number你是大于零的,那自然而然的就走到这儿了。你就消费就可以了。目前呢?就没啥问题了是吧,好,那我们上面呢,就可以来写具体这个啊,测试呢是写到最后了啊。哎,在这我们去写这个测试啊,May方法进来,首先呢,我们先把这个CRK呢,先给大家造出来。这个Clark呢,其实它本身呢,就充当了这个共享数据了。然后呢,我们去扭一下叫。User啊,这里边呢,把这个呢给它传进去。哎,进来。Out,回车。这个呢叫producer。
42:01
L1吧。就他啊。好,这个呢,就是我们说的第一个,呃,生产者,然后呢,再来一个消费者。啊,RK那扔进去。Alt回车。啊,CONSUMER1啊,就这样了。哎,接着呢,你可以在这给大家去起个名哈,叫set,一个name,这叫生产。生产者一。在那个内,哎,消费者一,诶可以了,好,然后呢,我们让他们各自呢去调start方法。好,那么他俩呢,就分别的开始去执行各自的这个run方法。开始去做这个执行了。好,来,我们先跑一下。嗯,这样的啊。好大家看这个时候的话呢,消费者,消费者开始消费产品,你看开始消费产品,其实这是消费者开始执行的啊,但是呢,他刚开始消费的时候呢,发现呢,没有这个产品是吧。
43:07
没有产品的时候呢,他就给wait了。那他喂以后呢,没有唤醒机制啊,所以生产者呢,就是一顿生产,他生产到第20个的时候呢,他也wait了,所以这个时候呢,我们就在这儿等待了。相当于呢,我们没有唤醒机制。那我们得考虑唤醒了啊,比如现以这个生产来说吧。这个什么时候唤醒的?嗯。他唤醒注意,他唤醒的是对方。那你就唤醒的是消费者是吧?消费者呢,这块一上来他就想消费,一看没有,他就等待了,只要你生产了一个。说你可以醒一醒了,说我这块有东西可以吃了是吧。在这呢,是不是来一个唤醒。诶唤醒好,这呢,我就闹ify了。诶OK,那对应的这块,诶如果呢,我们这个生产者呢,已经生产到20个了,然后他就给wait了,然后消费这块呢,只要他消费一个。
44:02
就可以唤醒这个。生产者是吧?哎,所以说呢,来一个这个notify,对这就可以了,来我们去做一个run。你看这时候这个程序呢,它其实就停不下来了。因为咱们这个生产消费呢这块呢,基本上都是差不多的啊,都是50嘛,生产呢是50毫秒生产一个消费呢也是类似,所以呢,基本上它就生产一个消费一个就就在这儿了是吧。就停不了了啊,那咱们。生产的快一点吧。这个呢比呃生产的快一点,把这个改慢一点也行。这个改成100。那这个呢,就相当于生产的要快是吧,诶run一下这时候你看是什么效果呢。你看这个数量呢,就上去了是吧,因为生产的快嘛,所以他这块到20的时候呢,他其实会有这个等待的这个行为了,呃,所以呢,这个他就得老等,然后消费者的话呢,就是只能是到20这时候他消费一个,消费一个呢,能生产一个,但是你这块再想生产就得等着,所以一直就在20这儿了。
45:03
不行,就咱再整一个消费者呗。可以呢,你再去复制一份,来一个二这呢也复制一份。来个消费者二,这样再复制一份,哎,来个二。也就这么着了是吧,哎,再去run一下。其实又差不多了。对,因为俩消费者呢,各100毫秒,相当于呢,平均一下不又又50毫秒一样是吧。但是这块呢,还有一个点啊,这块我们唤醒的时候呢,你可以考虑呢,这个呢就notify all了吧。是吧,就是你要要把把两个这个消费者呢,都给他唤醒一下,万一要都睡了呢,是吧?然后另外呢,还有一个小的细节,哎,我说呀,呃,咱们的生产者如果呢,把这个消费者呢,两个都唤醒以后啊,那么两个消费者呢,就会从被wait的位置呢,继续向下执行,那两个消费者的话呢,呃,有可能这块我们就只有一个商品了,他呢,就消费了第一个商品减减了,然后另外呢,一个消费者的话呢,他就又去调这个了,这时候就不太合适了,诶怎么办呢?所以这块我们还有一个小的细节呢,需要大家去处理一下。
46:05
咱们呢,需要把这个逻辑呢,诶把它呢放进来,就是这块呢,两个消费者呢,如果他唤醒以后呢,他重新的再进来去握这个锁这样的一个方式啊OK呢去处理。行,那对应的话呢,我们上边这个逻辑呢,也稍微的给他去微调一下。啊,我们把它呢,也放到相应的这个else里边,这样处理的话呢,就会更加的严密一些,OK啊大家这块呢,体会一下,像这个写完以后的话呢,我们再去做一个run。OK啊,那么整个的咱们这个程序呢,就没有问题了。行这块呢,大家体会一下。好,这呢就是我们说的生产者消费者问题,大家下来写一写,它呢能够把我们这一章呢,涉及到几个点呢,就都能够穿起来。
我来说两句