301
社区成员
发帖
与我相关
我的任务
分享考虑到第一次博客写的实在是太复杂,第六次作业的博客我希望能够按照更加清晰的结构来叙述我的设计。在这次博客中,我将解题思路单独叙述,再进一步叙述我的设计架构。
(查看个人博客以获得更好的体验)
重述题目,熟悉题目的话可以跳过这一部分。
课程组提供了两个标准。一个是正确性评判标准,一个是性能评判标准
课程组提供了较为复杂的评估标准,不太容易懂。简单来说就是满足以下几点要求:
只叙述有改动的地方:
RECEIVE语句,也就是要输出一些东西表明乘客分配到了这部电梯。RESET命令,RESET通过标准输入输入进来,电梯必须在5s内响应RESET请求,并且在这5s中最多移动两层楼(输出两次ARRIVE)。响应RESET请求的标志为输出RESET_BEGIN语句。RESET需要1.2s完成,并在完成时输出RESET_END语句表明RESET完成。RESET是用于设置电梯的新的运行状态的,这包括移动一层楼的速度和满载人数。RESET期间电梯里不能有人(可能需要踢人),RESET要在关门时才能RESET。RESET期间电梯不能接受新乘客。RESET后需要重新RECEIVE,当然这也意味着电梯RESET后可以原本的乘客可以不再坐这部电梯。和上次作业几乎完全相同。
只叙述有改动的地方:
RESET后转变为双轿厢电梯。RESET请求,用于将电梯重置为双轿厢电梯,RESET用时1.2s。相当于把单轿厢拆掉新建了一个双轿厢电梯。第二类RESET请求包括双轿厢电梯的换乘楼层、移动速度、最大人数等。RECEIVE请求时仍然可以移动。轿厢A和上层轿厢轿厢B,轿厢A只能在换乘楼层及其下方楼层移动,轿厢B只能在换乘楼层及其上方楼层移动。轿厢A默认在换乘楼层的下面一层,轿厢B默认在换乘楼层的上面一层,RESET。输入保证不会出现这种情况。OUT)就需要重新RECEIVE。具体的代码设计架构在第四部分。不想看解题思路的可以跳过。
纵观题目,我们可以得出一个简单的结构设计:
所以基本上,我们需要有这么几个类:
InputThread 输入线程 - 采购Schedule 分配线程 - 把人分给电梯吃Elevator 电梯线程 - 吃人吐人Client 顾客类 - 人RequestQueue 请求队列 - 采购但是没有被分配的人WaitQueue 等待队列 - 分配但是没有被电梯吃掉的人Strategies 策略类 - 决定电梯先吃哪个人先理一理要求和思路:
sleep(200)休眠200毫秒。考虑到休眠期间电梯是不会进行输入的,我们可以再新建6个电梯管理线程。这是一个很奇怪的问题,在吃了苦头之前我甚至都没有问过自己这个问题。
电梯单元采用的多线程设计模式比较简单,即生产-消费模式。在我的设计中,生产消费关系有俩:输入和调度、调度和电梯。
输入进来的乘客需要交给调度线程,由调度线程经过合适的调度策略(在这次作业中,调度策略就是把乘客调度到它指定的电梯)交给电梯。因此输入和调度线程构成生产-消费关系:输入不断生产客户,调度器不断消费客户。而存放生产出来的产品的地方正是RequestQueue类。
调度器会将自己得到的客户交给电梯,由电梯进行消费。从输入进来的顾客会在输出后被释放,因此到这里为止搭乘电梯的顾客构成的供应链就到达链的末尾了。存放生产出来的客户的是我们的WaitQueue类,也就是在指定电梯面前等待的客户构成的集合。这样的集合一共有六个,每个电梯各一个。
写这个板块纯粹是因为我在synchronized方法上吃了苦头。总结起来我最开始不理解的地方大概就只有这么几点:
synchronized部分一次只能由一个获得了指定锁的线程运行。这种指定的锁只有一把,当这个线程在运行的时候它就占有了锁,其他线程必定就没有占有锁,因此其他线程必定无法运行。synchronized锁就是一个对象。同步方法的锁就是这个同步方法所处的对象本身。静态同步方法锁的是类对象本身,简单理解就是这整个类的所有静态同步方法共同竞争的一把锁,它不会影响非静态同步方法。notify,但是有一个情况例外:就是这个同步方法是被另一个同步方法调用的。这时这个同步方法哪怕结束了,锁也不会被释放。这时不需要notify,否则就轮询了。详细见第五部分“五、调度策略选择”
第六次作业的主要改动有两个方面:一个是RESET,一个是乘客不知道进哪个电梯,需要调度器去调度乘客。
我们来分析一下我们现在的线程情况。
首先,一个输入线程,一个调度器线程,六个电梯线程,这八个线程应该是没得跑了。六个电梯显示关系实在是太复杂了,我们先考虑一个电梯,也就是先考虑3个线程先。(直觉告诉我们,六个电梯彼此应该没有太多的关系。)

然后检查一下这三个线程的关系:

其中,箭头表示生产-消费传递链。然后可以分离出电梯这个模块:
+------------------+
| **Elevator** |
+------------------+
+--------------------------------|--> ResetQueue |
| | |
Input +--> Scheduler ----|--> WaitQueue |
| | | |
+--> RequestQueue <--------------|--- KickOut |
+------------------+
和第五次作业不同,第六次作业中电梯不再只是消费者:被电梯踢出来的乘客会重新进入调度器接受调度,因此电梯在某种意义上也是生产者。
生产和消费构成的闭环会导致一个问题:电梯和调度器线程根本停不下来! 调度器需要查看RequestQueue判断是否停止:如果RequestQueue为空并且结束了就把电梯也停下来。RequestQueue的结束是依赖于电梯线程的,电梯线程只要不停,RequestQueue就停不下来,调度器也停不下来,进而电梯也停不下来,使得程序无法结束运行。
为什么会出现这种情况呢?因为在闭环中只查看线程之间存放乘客的共享对象,是无法给生产消费链画上终止符号的。我们需要更多信息。对于调度器线程来说,它不仅仅需要知道共享对象的状态,还需要知道电梯线程的状态才行。再调度器得知输入线程结束了之后,调度器线程还需要确认电梯将所有的乘客都送完了才行。
所以解决方案很简单:调度器如果发现输入线程结束了,并且没有人需要调度,并且每个电梯都没有人了,就让所有电梯都停下来,并且自己结束运行。
详细见第五部分“五、调度策略选择”
第七次作业是最后一次重构的机会了!我把握住了,然后写了一tuo...
个人认为第七次作业可能会遇到这些问题:
第七次作业中的电梯线程的管理其实很简单:创建单轿厢电梯、销毁单轿厢电梯、创建双轿厢电梯。所以主要矛盾集中在单双轿厢电梯的切换上。很难让单轿厢电梯创建双轿厢电梯(因为逻辑上很奇怪)所以强烈建议加入一个中间层来管理所有电梯线程 (比如H Boy提出的创建电梯井 ) 。线程的创建和销毁都是调用中间层的方法来完成的。
吴老师在第八次课提到了线程池,但是我没有使用过,并不清楚。
换乘楼层就是临界区,临界区应当配备一把锁。进入临界区就要获得锁,否则等待。离开临界区就释放锁。
个人为单轿厢和双轿厢总共开了三个线程。第二类RESET指令在进入单轿厢电梯之前先会经过一个电梯和调度器之间的中间层。中间层负责告诉单轿厢电梯进行重置,同时创建两个轿厢共同构成双轿厢电梯。在双轿厢电梯的重置过程中,电梯会占用“加入等待队列”和“添加重置信号”的锁,防止在电梯重置过程中有重置信号和等待队列被加进来。同时电梯对外界显示为“处于重置状态”,防止调度器或者输入线程在向点题输入乘客或者重置信号时被阻塞(他们可以提前查看电梯状态来判断电梯是否在重置,并调度乘客到其他电梯)。
这是代码架构分析,不包含任何解题思路。解题思路请看第三部分“三、解题思路”
第五次架构设计比较简单,实现的功能也比较简单。整体设计从UML协作图中基本可以看的清清楚楚,个人认为没有太多地方需要特别叙述。解题思路
在网上搜了一圈,感觉协作图(通信图)五花八门。课程组要求中的应该是时序图(sequence diagram),但是看到要求的时候已经没有时间画了这里画的虽然不是要求的时序图,但是个人认为还是能够比较清晰地反映各个线程之间的协作关系的。第五次作业的协作图比较简单,到了第六次作业时序图应该就能够看得比较舒服了。

整体上就是一个生产消费模型,输(gong)入(chang)线程生产乘客,交给调(tao)度(bao)平台调度,最后交(mai)给电梯消费掉。


所有圈复杂度较高的方法我都看了一圈,事实就是if-else及其嵌套太多,用于各种条件判断。每一个if-else里面的内容就两行:一行设置下一个状态,一行return。我当然可以拆开这些if-else,然后把每一次嵌套都封装成一个私有方法,但是我并不认为这会让可读性变高,反而可能会过度封装。此外可以发现,复杂度过高的方法其实都被私有化了。

我承认Strategies类写的确实不太好,不过我已经不知道怎么优化了。至于Elevator类和WaitQueue类我倒是不是很担心,我在写一个类的时候会根据外界需求在里面封装好许多好用的方法,哪怕这些方法可能并不会用到,就有点像自己搓一个小的库一样吧。这使得方法复杂度很高,但是实际上既不影响可读性,我也不认为与封装的思想相矛盾。在之后的作业中我沿用了这个类,导致复杂度一直降不下来。
另一方面,由于我设置了UP和DOWN两个状态,导致几乎所有和上与下有关的代码都会高度相似、对称。后面我了解到原来枚举类型也是可以设置方法的,这样子我或许可以将这部分代码简化不少。
整体上分了三类线程类和三类共享对象,具体如下图所示。个人认为整体还是比较清晰的。


仍然是策略类的方法复杂度过高,过高原因同第五次作业相同:单纯因为需要很多的条件判断。但是整体上可读性还是蛮高的:各种if-else判断整体来说看得还是比较清楚的。
其次复杂的事Elevator类中的actWait()和run()方法。actWait()由于设计遗留的历史原因,他和run()方法其实有一部分代码高度相似。当时在写的时候想了半天没有想明白应该怎么设计多线程的终止条件,于是写了一个很复杂的终止条件判断,最后结果是放在Elevator.run()也觉得怪,放在actWait()中也觉得怪,所以就把两边的代码都保留了。实际上终止条件并没有这么复杂,不过我在写完第三次作业之前都不知道要怎么设计,算是一个小遗憾吧。
SchStrategy 的 onTheWay() 方法由于时间问题,虽然写了但是没有启用,姑且不认为它复杂。TestMain是用于测试的代码,未提交至平台上。

WaitQueue的复杂度来源于各种不同限制条件下的查询和检索:查询高于某一楼层的希望向上走的乘客、查询该层楼下所有向下的乘客等等。每个方法并不复杂,但是由于方法实在太多,复杂度不得不上来。
Elevator类的主要复杂度就是上面提到的几个复杂度较高的方法。删去后方法复杂度马上降下来了。
EleStrategy的方法复杂度和第五次作业的Strategies复杂度完全一致,不过多叙述。
第七次作业的主要改动在于封装了电梯。
在第五次和第六次作业中我一直是让线程对象直接调用共享对象中的方法来完成添加乘客,删除乘客等操作。但是后面发现似乎并不一定需要直接调用共享对象的方法,而可以将共享对象封装到其他对象中,调用其他对象的方法来完成。
比方说,如果调度器要将乘客加入到电梯中,我们会说:“把这个家伙分配给这个电梯!”而不会说:“把这个乘客放到这个电梯的等待队列中。”对于调度器来说,前者的思想更符合我个人的直觉。
这有什么区别呢?前者我们将电梯的等待队列的概念隐藏了,电梯对于外界而言没有等待队列的概念,外界只知道自己将一个乘客分配给了这个电梯。但是后者却将电梯的等待队列暴露了出来。
直接上协作图吧。

关系个人认为还是很清晰的。主要是添加TrashCarrier现成将电梯完全封装了起来后结构变得十分清晰。
类图则稍微复杂了一点。我删除了所有私有化方法(继承接口是一点不会啊)

从中可以看出虽然加入了Elevator中间层,但是这没有降低多少设计复杂度。单轿厢类、电梯线程类和电梯中间层类应该有更清晰的层次关系,但是当时设计没有想太多,于是就写成这样了。

EleStrategy即为先前的Strategy和Strategies类,复杂原因已经在前文中叙述过,在此不再叙述。
TestMain是测试的一部分,不属于项目,也不会提交至评测平台。
ElevatorThread.run()的主要复杂度来和第六次作业一样,源于结束条件的判断。不过相比第六次作业已经有了很大程度上的降低。
Elevator.outputString()是一个用于输出的静态方法。对于单双轿厢电梯,双轿厢的上下层电梯都需要特判后才能进行输出,这导致设计的圈复杂度增加。

EleStrategy的复杂度同第六次和第五次作业一致,不再过多叙述。
ElevatorThread的复杂度是我没料到的,我坚持认为它不复杂。大概就是:认知复杂度高的方法essential圈复杂度都很低,

这一部分是关于具体的调度器调度策略,以及电梯的调度策略的叙述和我的选择。
经过第一单元的淬炼,我想第五次作业应该会有各种各样的神奇策略能够被想出来。但相比比第一单元整体来说没有太多逆天优化。大多数人使用的都是LOOK策略。这里叙述几种常用的策略和我的选择:
第五次作业调度器没有任何调度策略。调度仅发生在单个电梯中
我至今也不知道ALS到底是什么的缩写,但是只要在互联网上搜索“ALS调度策略”,我们一定能找到北航计院的身影。
ALS的调度策略很简单:
ALS的缺点很明显:ALS代表了一类算法,或者说一类算法的原则:优先接最早到达的人。这是ALS的整体原则,也是我们在设计时可以考虑的原则。
但是ALS并不是那么好。比如:
Example 1: 假如有一大波同学1楼到9楼,他们先按了电梯;而有另一波同学想从10楼到1楼,但是他们全部都按晚了电梯。
根据ALS,他们的请求不会被响应,直到前一波人的请求全部被完成—。当电梯到9楼,电梯宁愿去接最早到的乘客,也就是1楼的乘客,也不愿意接10楼的乘客——哪怕电梯只需要上行一层楼!
事实上,不加改进的ALS策略的实(qiang)际(ce)表现也不佳。这就出现了SCAN策略和LOOK策略。
回想一下我们现在乘坐的电梯,在Example 1中二楼的乘客每次上电梯的时候应该会发现有一大波人从电梯里涌出来。这是因为电梯采用了LOOK策略。在讲LOOK之前,先讲一下SCAN策略。
SCAN策略做的事就是在最高层和最底层之间来回扫。SCAN没有主请求,但是顺带着却能够接到更多人。在Example 1当中,他们会先去接10楼的乘客——不是因为10楼的乘客优先级有多高,只是因为电梯会运行到最高层(11层)再往下运行,而往下运行的时候就正好能够捎带10楼的乘客。
但是11楼是没有必要去的,因为11楼没有人。这就引出了LOOK策略。**LOOK策略意在解决SCAN中的无效运行。在LOOK中,我们只需要找到最高和最低的有需求的楼层就行了**:不需要到11楼,而是到10楼就行。
通过这个例子我还想说明一个问题,那就是只做大优化不做小优化。当你以为LOOK比SCAN好的时候,请思考这么一件事:如果在LOOK接到十楼的乘客并且开门了打算往下走,这时如果有一个十一楼的乘客刚刚按电梯,那这个十一楼的倒霉蛋就要在等一轮了。也就是说,无用的运行有可能会有用,这取决于具体输入。不要为一些小细节优化,总是要尝试进行大优化。
LOOK策略是一种非常好的策略。在第六次和第七次作业中,由于调度器会将乘客分配给不同电梯,很难出现一个电梯会有很多很多人的情况(数据限制最多100个乘客,平均下来每部电梯不超过17个乘客,很快就能送完)。所以LOOK的弊端再次看来并不会这么明显。
LOOK和ALS不仅仅是两个具体策略,他们更反映了两个思想方法或者原则:一个认为最早来的人最应该先响应,另一个认为我自己走我的路,方便我响应我就响应。LOOK 原则和 ALS 原则各有优劣:LOOK很容易使某一个人等待时间过长,而ALS可能会对某一些按按钮晚的人很不公平。
这两个原则可以结合一下,取长补短。比方说,我们可以优先响应同方向上到达时间最早的人,在保证能够接到它的前提下再去捎带其他人。这样子在一个方向上很难出现等待时间过长的人,进而会更好。
我认为LOOK + ALS并不足以满足乘客的需求,因为这种算法只能考虑当前情况下等待时间最长的人,却不能考虑当前情况下多个等待时间很长的人。当一个方向上有很多等待时间很长的人的时候,而我们在没有接到他们之前接了其他捎带的人,那么这时候我们很可能就只能接到一个等待时间很长的人,也就是主请求。而其他人等待时间明明也很长,却因为在电梯接到他们之前捎带了其他人,因而无法搭乘电梯。
坏心情算法认为造成这样的原因是因为主请求太少了。 如果有多个主请求,我们就可以先保证先去接这些主请求,再去考虑是否捎带。
在我的算法中,请求的优先级是根据乘客的心情的好坏来决定的。我规定,如果电梯在一次捎带过程中明明可以和我同方向经过了我的楼层却不捎带我,那么我的坏心情就+1,也就是我的优先级在电梯眼中就提高了1级。这种算法来源于使用新北电梯的真实感受,我把它叫做坏心情算法。
坏心情算法解救了LOOK + ALS算法中两个等待时间较长的人可能只能接到一个的问题,实际上如果稍微更改一下坏心情算法,将时间而不是坏心情作为最高优先级,我们就得到了另一种LOOK + ALS算法了。
坏心情算法的缺点也是很明显的:它几乎完全被坏心情的人带偏了。如果坏心情的人比较多,并且这些气急败坏的人的请求比较苛刻,那么它们可能会占据电梯的所有位置,导致捎带的人减少,进而导致电梯要花费更长的时间才能够送完所有乘客。
方法提出自研讨课同学N Boy。膜!
传统LOOK认为只接同向可捎带的乘客。但是实际上在乘客较少的情况下,LOOK可以两个方向上的乘客都接上,以减少开关门时间。
没有最好的调度策略,所有的调度策略都有反例,因为未来是未知的,人流量、目的、到达时间就无法纳入考虑因素当中。再退一步讲,即便我们能够知晓未来的一举一动,判断出最佳策略也是困难的。因此经过第五次作业后,我同意指导书的观点,最好的策略就是变换策略。不过真的麻烦死了......
在第六次作业中,乘客不再指定进入电梯号,而是由同(wan)学(jia)自行调配。因此在第六次作业中有两个策略需要完成:一个是把乘客分配到不同电梯的策略,另一个是电梯接送乘客的策略。由于电梯在没有RECEIVE的时候不能移动,因此不能让电梯自由竞争乘客。
第六次作业中的调度策略整体来说大概有这么几种构调度策略/原则:
顾名思义,把乘客依次分配给1, 2, ...6号电梯。性能十分均衡,性能分也很一般。
随机将乘客分配到不同的电梯。实测随机调度大概能够在强测中获得 $\ge 96$ 的优秀性能分。再在基础上做一些小优化就可以拿到很不错的分数了。
调度的随机性意味着得分的随机性,不清楚课程组会不会反复测试代码然后取平均,但是本地运行多次可能会有不同的效果。乘客量和乘客密度大的时候几乎不会有分数波动。
方便捎带就能捎带,结合LOOK类算法可以有不错的性能。不适用于坏心情这一类复杂的算法,理由是很难计算能不能捎带这个人。
实际上,根据现有的所有信息,我们是能计算出乘客应该分配到哪一个电梯最好的。我们只需要模拟一遍电梯的运行,算出把乘客分配到哪个电梯更好,然后分配给电梯就行。而且这个分配的时间是由调度器完成的,意味着分配不会占用电梯运行时间。这是最理想的贪心算法。由于模拟过程好似有一个和实际电梯完全一样的电梯在运行,所以称模拟的电梯为影子电梯。
影子电梯的模拟也是分好坏的。优秀的影子电梯可以做到强测 $\ge 99$ 分。
影子电梯也有模拟不到的地方,比如很难将RESET用时考虑进影子电梯中。
单电梯的调度策略和上次一样,LOOK就行。
几乎没有变化,本人第七次作业摆大烂。至于调度策略,基本上同第六次和第五次作业。
由于多线程实在是让我有点摸不着头脑,我在三次作业中都进行了大规模重构。
EleStrategyWaitQueueClientInEle解决方案:使用同步方法。
解决方案:那就根据LOOK策略选择一个方向。
解决方案:分析线程的关系,只在容易产生冲突的地方进行同步;私有化方法
解决方案:自己手动解决冲突就行,应该相当于commit一次再与上一次提交merge。
我为不同类写了一个debug()方法,但是问题是难以判断调试信息输出的位置。搜索后发现下面这行代码可以打印当前Thread运行位置。评测中检测非法字符串的时候检测到了这一行代码。
System.out.println(Thread.currentThread().getStackTrace(](1].getMethodName())
这在第六次作业和第七次作业中比较常见。RECEIVE不能再电梯的RESET期间输出,否则错误。第六次作业误打误撞把冲突解决了,导致第七次作业出现这个报错的时候我都不知道原来还有这项要求。
解决方案:加一把锁,RESET期间持有锁,完成RESET后释放。简单来说就是电梯必须在外界看来永远都是在正常运行的。如果在不正常期间运行的话,外界对电梯的访问就会被阻塞住。
注意事项:另一种解决方案是通过查看电梯的运行状态来判断要不要等待,如果电梯在RESET状态就等待,直到被notify()。这样的实现是不稳妥的,很有可能在你查看状态的下一瞬间电梯就完成RESET并且notify(),这样子就查看电梯状态的线程就永远不可能醒过来了。如果一个线程的运行依赖另一个线程的状态,那么最好用锁。
在RESET_BEGIN之前电梯需要把分配到这部电梯的乘客交给调度器,调度器会调度乘客,乘客会被分给新的电梯并输出RECEIVE。这看起来没什么问题,但是这一切都可能在一瞬间完成,然后下一瞬间电梯才输出RESET_BEGIN!意味着电梯还没有RESET_BEGIN时,另一个电梯就抢走了原本电梯的乘客! 第六次和第七次作业不允许出现这样的行为。
解决方案:我的解决方案比较奇葩。我会先让乘客从电梯轿厢中假装出来(输出开关门和出电梯但时在对象的数据中仍然存在于电梯轿厢中),然后输出RESET_BEGIN,再把乘客送给调度器。
电梯的重置RESET有至少两种实现方式:一种是将RESET作为一种属性写入电梯中,电梯每次切换状态需要查看是否处于需要RESET的状态,需要则RESET。但是如果有多个RESET指令同时涌进来的话,前一条RESET指令就会被覆盖掉,不过由于第六次和第七次作业的约束(同一电梯的两条RESET不能有太近的时间间隔),这种情况几乎不可能发生。
另一种实现方式是将RESET看成另一种产品传递给电梯,构建区别于乘客这种产品的另一条生产-消费供应链。这就产生了多产品的生产消费模型。这给控制设计带来了不少的麻烦(自己写一遍就知道问题了)。
解决方案:让多产品的输入共用一把锁,使用Check-and-Act行为进行设计。
解决方案:具体停不下来的原因千奇百怪,设置合适的终止条件基本就能停下来了
多线程Debug是一件麻烦事,不过IDEA帮我们做了不少工作。IDEA中右键断点的红点点可以选择默认多线程Debug,因而在每一个线程运行到这一行代码的时候都能够停下来(不过我倒是很好奇gdb有没有这种功能)。缺点就是实际上运行时不会有线程停下来,所以导致运行和调试可能带来不同。即便如此,Debug还是麻烦的:Debug效率本来就低,现在还要同时在六个电梯线程中打断点,这我真的受不了一点。所以我直接print大法调试。我在每一个我需要用到的类里面写了一个debug()方法,用于往终端输出该对象的信息。另外建议将printf大法打印出来的信息直接输出到标准错误输出,因为IDEA中标准错误输出是红色的。
在第六次和第七次作业中我写了一个Debug类(里面全是静态方法),所有的printf函数都是调用Debug类的静态方法完成的。并且在Debug类中设置了一个参数valid,输出当且仅当valie = true,否则不会输出debug调试信息。
首先印象最深的是关于synchronized关键字。synchronized的同步语句块或者同步方法只需要在共享对象上使用,或者更一般地,只需要在多个线程产生访存冲突的地方使用。合适的synchronized可以更好地保证程序的正确性和安全性。在第五次作业中,由于滥用synchronized关键字,我得到了非常糟糕的第五次作业初稿。
此外,这次作业也算是掌握了一些基本的多线程编程的基本思想。生产-消费模型是多线程或者并行编程中的一个最基本的模型,电梯分配属于他们的一个简化版本:没有产品上限,不需要考虑生产消费速度,也几乎不需要考虑某一个电梯闲置时间过长的情况(顶多强测分数低一点)。此外,JAVA提供了非常舒适的多线程编程方法,在其他并行编程模型中不一定会有这么舒适的选项。
最重要的,应该是对多线程有了更深的理解吧。个人感觉这种多线程编程、并行编程的编程方法,在日后的编程学习中应当会有非常好的应用,而且不一定会有机会再接触到了。
不知道第三单元和第四单元学些什么,但是第二单元多线程确实蛮有用的,如果后两单元没啥大事的话个人认为其实可以把这一单元延长一点...点。
(好像还是写了太多废话了...)