北航20级oo课程第二单元总结

第二单元总结

  第二单元要求我们搭建一个多线程的电梯系统,由于是第一次接触多线程问题,在设计代码架构时,很容易会出现线程安全问题,这类问题在复现上十分困难,因此非常考验我们对线程安全的理解。下面我将阐述自己的作业设计。

hw5:

作业要求:

  要求实现纵向电梯的调度,保证乘客出发楼座与目的楼座一致。

作业设计:

  第一次作业比较简单,线程之间的交互较少,本次作业共有两个共享资源,一个为输入缓冲(TableBuffer类),另一个是各个电梯的等待队列(Table类),TableBuffer负责暂存控制台输入,由主线程和TableControl共享,实现读写互斥。Table类在Lift被创建时,作为类成员在Lift内创建实例,由LiftControl和TableControl共享资源,读写互斥。电梯策略为LOOK策略

UML图:

时序图:

hw6:

作业要求:

  本次作业,在上次作业基础上增加横向电梯,乘客只能在同层移动或同座移动。与上次作业没有本质上的区别,无非就是增加一个电梯类和对应的table类,但是横向电梯的行为与纵向电梯稍有不同,横向电梯没有最高最低楼层限制,可以从E座直接跨到A座。

UML图:

时序图:

 

LOOK策略调整:

  针对横向电梯,需要对LOOK策略做出调整。

  1. 横向电梯初始运动方向向右
  2. 判断当前座是否有人下电梯,开门放人,之后判断是否有同向乘客,开门接人
  3. 若点梯无人,判断当前座是否有反向乘客(condition1),判断电梯前方两座是否有同向乘客(condition2),若condition1 || !condition2,电梯转向,开门接人
  4. 电梯移动
  5. 返回step2  

  在此说明,此策略在电梯无人时,优先满足当前楼座的请求(不管后两座的同向请求,只要当前楼座无同向请求且有反向请求,电梯掉头),这与一般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这个方法外,还有其他更加灵活的加锁方法,可惜没能在作业中予以实践,而且由于时间原因,在作业中,自己基本上是在闭门造车,没有参考学习其他优秀的架构设计,属实有些遗憾,希望以后课程组能够在单元结束后放出一些优秀代码供参考学习。

 

posted @ 2022-04-27 23:18  白松鼠  阅读(69)  评论(1编辑  收藏  举报