面向对象第二单元总结

一、第一次作业

共Elevator、FloorQueue、Input、Main、RequestQueue、Schedule六个类。

分别为电梯、托盘、输入、主程序、用户、调度。

线程交互方式

在第一次作业中,一共有三个线程。除了主线程,还有读入线程和电梯线程。读入线程将信息放进托盘当中,电梯管理器从托盘中读取信息。读写的时候对托盘加锁,保证不会出现同时读写托盘的情况。

两个线程共享托盘。读入线程可以对托盘进行写操作,电梯线程可以对托盘进行读写操作。因此每次都要对托盘进行加锁。当电梯为空且托盘为空时,电梯线程进行wait并释放锁。当读入读到一个请求或所有请求终止后,要进行notifyAll。

调度器设计

第一次作业只有一部电梯,所有请求都需要交给这部电梯。

电梯采用的基本上是Look算法。当某个方向的请求全部处理完后进行转向。当托盘中有多个请求可以放入电梯时,按照到达顺序依次进入电梯。

由于纸片人厚度为零,最优策略是让所有人在电梯开门的一瞬间出电梯,关门的一瞬间进电梯。这样可以优化电梯在某一层开门时来人的情况,让更多人一次进入电梯。

二、第二次作业

 

共Elevator、FloorQueue、Input、Main、RequestQueue、Schedule六个类。

分别为电梯、托盘、输入、主程序、用户、调度。

线程交互方式

第二次作业是多电梯。除了主线程,还有读入线程、调度器线程、电梯线程。交互请求分两种,一种是乘客请求的交互,一种是电梯请求的交互。

乘客请求:读入线程和调度器(Schedule)线程之间,通过缓冲区FloorQueue来进行交互。调度器和每个电梯通过各自的小托盘来交互。当读到一个乘客请求时,读入线程向FloorQueue中加入这一请求。当FloorQueue中有请求时,调度线程按调度策略将请求分配给某一RequestQueue。

当读到一个电梯请求时,读入线程直接将请求发给调度器,在调度器中创建一个新的电梯线程。

 

调度器设计

如果存在一个电梯,这个请求在电梯当前的方向上,且电梯中的人数+电梯当前所在方向的人数小于电梯容量,就将请求退给所有满足这个要求的电梯中,与目标请求最近的一个。否则将请求推给所有电梯中,电梯人数+RequestQueue最小的一个。

 

三、第三次作业

 

共Elevator、FloorQueue、Input、Main、RequestQueue、Schedule六个类。

分别为电梯、托盘、输入、主程序、用户、调度。

 

线程交互方式

第二次作业中的全部交互,在第三次作业中仍然存在。在此基础上,由于第三次存在换乘,也就是存在从电梯向大托盘输出需求的情况。因此线程的交互,要增加从电梯直接到大托盘的信息输出。

如果在电梯里判断出需要向大托盘吐的需求,就直接输出到大托盘,则会发生死锁,具体在后文中会详细介绍。因此,需要采用别的方法来解决这一问题。

控制器是先对waitingQueue加锁进行操作。在这个代码块中得到需要吐出的需求。如果在这个代码块中将需求直接加入peopleSet,则需要在这个代码块中再对peopleSet加锁,产生B锁中等A锁。

因此,这样可能会触发死锁。

为解决这一问题,我让电梯的需求先都吐到popQueue中,等释放了waitingQueue的锁以后,再去拿peopleSet的锁,将popQueue中的请求全部给到peopleSet。

调度器设计

第三次作业的调度是比较复杂的。为了通过弱测与中测,一刀切不使用新增电梯。而在之后的迭代中,加入换乘策略,根据人数、等待时间、运行速度等进行综合考量,但最终因为某些策略之外的问题失败。

 

可扩展性分析

对于新的电梯类型,基本不需要做什么改动。由于已经很好地实现了请求的拆解和乘客的换乘,继续扩展将变得十分容易。

 

四、心得体会

线程安全

线程安全主要包含两个方面,一是程序运算的正确性,这一方面可以通过加锁来解决。在此情况下会出现第二个方面:程序运行的正确性,例如程序中避免出现死锁等影响程序正常运行的机制。为此,我们要控制加锁的数量与位置。

对于生产者-消费者模式来说,两者共用一个托盘/缓冲区,在读写的过程中,对这个托盘/缓冲区加锁,就能实现线程的安全,并避免死锁。

 

posted @ 2021-04-27 22:00  FGmaster  阅读(66)  评论(0)    收藏  举报