第二单元总结
第二单元要求我们搭建一个多线程的电梯系统,由于是第一次接触多线程问题,在设计代码架构时,很容易会出现线程安全问题,这类问题在复现上十分困难,因此非常考验我们对线程安全的理解。下面我将阐述自己的作业设计。
hw5:
作业要求:
要求实现纵向电梯的调度,保证乘客出发楼座与目的楼座一致。
作业设计:
第一次作业比较简单,线程之间的交互较少,本次作业共有两个共享资源,一个为输入缓冲(TableBuffer类),另一个是各个电梯的等待队列(Table类),TableBuffer负责暂存控制台输入,由主线程和TableControl共享,实现读写互斥。Table类在Lift被创建时,作为类成员在Lift内创建实例,由LiftControl和TableControl共享资源,读写互斥。电梯策略为LOOK策略
UML图:
时序图:
hw6:
作业要求:
本次作业,在上次作业基础上增加横向电梯,乘客只能在同层移动或同座移动。与上次作业没有本质上的区别,无非就是增加一个电梯类和对应的table类,但是横向电梯的行为与纵向电梯稍有不同,横向电梯没有最高最低楼层限制,可以从E座直接跨到A座。
UML图:
时序图:
LOOK策略调整:
针对横向电梯,需要对LOOK策略做出调整。
在此说明,此策略在电梯无人时,优先满足当前楼座的请求(不管后两座的同向请求,只要当前楼座无同向请求且有反向请求,电梯掉头),这与一般LOOK策略不同,性能也比一般LOOK要低,但是这能够防止一类bug:
假设有五个请求:A->E,B->A,C->B,D->C,E->D;电梯方向向右,若采用一般LOOK策略,由于没有最高(最低)楼座的定义,电梯会不断按A->B->C->D->E->A的顺序在楼座间移动,接不到人。
hw7:
作业要求:
这次作业,乘客允许进行换乘,简单来说,一个乘客不能视为单独的一条指令,而是复数指令的集合,因此要求电梯现程与调度线程的进一步交互。
作业设计:
在此次作业中,我们对Person类、TableBuffer类、以及Lift类做出改进,Person类加入成员status,用于改变指令,TableBuffer类添加一个ArrayList<Person> alive,只要Person没有到达目的地,就不能从链表中删除。Lift在释放Person时,会使Person的status减少1,若status!=0,该Person会被重新加入TableBuffer的缓冲队列,否则,将Person从alive中移除。当且仅当输入结束,且alive为空,所有线程才能结束。
UML图:
时序图:
线程交互与线程安全设计:
由于这一单元主要的bug都集中在线程安全问题,因此本次总结略去bug分析部分,直接展示线程交互以及安全设计。这一部分主要展示TableBuffer的设计。
1 import java.util.ArrayList; 2 3 public class TableBuffer { 4 private static ArrayList<Person> persons = new ArrayList<>(); 5 private static ArrayList<Person> alive = new ArrayList<>(); 6 7 public synchronized void addBuffer(Person p) { 8 /*添加乘客*/14 this.notifyAll(); 15 } 16 17 public synchronized void clearBuffer(Person p) { 18 /*分配完成后,将p从缓冲中删除*/ 19 } 20 21 public synchronized void clearlive(Person p) { 22 /*乘客抵达终点,从alive中删除*/ 23 this.notifyAll(); 24 } 25 26 public synchronized ArrayList<Person> getTable() { 27 /*读取缓冲*/28 } 29 30 public synchronized boolean isFree() { 31 /*判断是否结束*/36 } 37 }
以上代码删去了一些细节,改用文字描述,本单元任务中,主要通过synchronized修饰方法以及代码块来保证线程之间的互斥。
在缓冲区中,主线程输入调用addBuffer,调度器访问getTable以及clearBuffer实现乘客的具体分配。同时调度器会调用isFree来判断是否结束所有线程。在Lift中,每当一个乘客下电梯,我们会改变其status,并调用addBuffer或者clearlive,我们保证每次调用这两个方法会唤醒等待中的线程,这样做保证了在程序末尾,调度器能够对isFree进行调用以发出终止信号(主线程输入结束会将调度器内部的mark成员置为false,终止信号为 !mark && isFree,为了防止主线程输入结束信号在isFree == true之后发出,因调度器线程wait而不能正常终止程序,主线程在输入结束后会addBuffer(null)以唤醒调度器),防止出现死等问题。
总结:
电梯月终于结束了,虽然掌握的基础的多线程知识,但是还不够深入,除了synchronized这个方法外,还有其他更加灵活的加锁方法,可惜没能在作业中予以实践,而且由于时间原因,在作业中,自己基本上是在闭门造车,没有参考学习其他优秀的架构设计,属实有些遗憾,希望以后课程组能够在单元结束后放出一些优秀代码供参考学习。