一. 综述

在本单元中我们初步学习了多线程的知识,熟悉实现线程安全的方法,逐步迭代开发实现了电梯调度系统。

UML类图如下:

 

 

UML协作图如下:

 

二.作业分析

 

第一次作业

要求:每栋楼各一部电梯,保证乘客不会发出跨楼座请求。

架构设计

架构和策略实现

采用生产者消费者模式,为5栋楼各设置一个Schedule用来处理发到该楼的请求,Schedule类维护一个elevator的list用以管理该栋楼的电梯。此时Schedule为生产者向ekevator发送请求,elevator为消费者消耗请求。设置一个RequestPool用以处理全部的输入,将输入的请求发送到相应Schedule,此时RequestPool为生产者,controller为消费者。对于具体的elevator而言,电梯采用的捎带策略为look。

线程安全控制

同步块设置为Schedule中的requestQueue由Schedule和RequestQueue共享,Schedule只读,RequestQueue只写。Elevator中的todo由Elevator和Controller共享,Elevator只读,Schedule只写。

线程安全退出条件为,全部输入结束时RequestQueue结束,全部输入结束且Schedule的requestQueue为空Schedule结束, Schedule结束且乘客请求处理完Elevator退出。

BUG分析

本次作业几乎不会出现bug,但对于线程安全输出还是存在bug。在阅读指导书时没有理解何为线程不安全的输出,由于System.out.println()本身是线程安全的,所以平常没有体会到这一点。解决方法是在Timeout.println()外加synchronized即可。

 

第二次作业

要求:增加了横向电梯,同楼栋可存在多部纵向电梯,可实现电梯的跨楼层操作。但是对于请求加上了较强的限制,只允许乘客在同一层楼或同一栋楼发出请求,不允许同时跨楼层和跨楼栋。

架构设计

架构和策略实现

沿用了上次的设计,由于本次作业横向电梯和纵向电梯没有交际,所以采用同样的方式对于横向电梯进行管理。设置一个controller维护floorElevator的list管理全部横向电梯。RequestQueue将请求分发给controller,controller再发给电梯。对于纵向电梯,采用controller调度给elevator分发请求,优先将请求发给可稍带的、较近的电梯,电梯无需处理请求调度和分发的工作。对于横向电梯也采用类似look的策略运行。

线程安全控制

同步块设置和上次基本一致,区别在于Controller会共享多个电梯的todo。

线程安全退出方式和上次相同,采用RequestPool--Controller--Elevator依次退出的方式结束程序。

BUG分析

本次作业出现了轮询的BUG,原因是调度策略写的逻辑有问题。最初的调度策略是判断当前Controller请求列表中的请求是否能由电梯捎带,如果能就分发下去。但是不能捎带的请求就会被留在队列中,导致队列被反复查询造成CPU时长过高。当时写的时候重点考虑了如果都能捎带的情况下该如何分配,忽略了不能捎带的请求的问题,在本地正确性检验是没有问题的导致疏忽了轮询问题的检查。

 

 

 

第三次作业

要求:增加了对横向电梯的限制,限制横向电梯可到达的楼栋,同时同一层可以有多部横向电梯。本次作业乘客的请求可以同时跨楼层跨楼栋。

架构设计

架构和策略实现

本次作业修改了调度策略,采取自由竞争的做法,controller不再作为一个线程,而只是保存请求列表的容器。调度方法为,根据当前的电梯到达情况维护一张图,对每个请求都在这张图里找最短路,将请求拆分为只有横向或纵向的请求,由这些请求构成一个请求list发送给相应controller,由最早到达且能够处理该请求的电梯抢到请求并处理,对于没有一次处理完的请求,再将请求列表发回相应controller,直到该请求被完成。

线程安全控制

同步块设置上将Controller的请求list暴露给其管理的电梯。新增了Count类用以统计剩余未完成的请求数,该类由单例模式实现,暴露给InputThread和全部电梯。

线程安全退出上,利用Count计数,当input结束并且全部请求都被做完时,唤醒全部电梯结束其线程。

BUG分析

没有看到指导书里可以允许同楼层存在多部横向电梯,导致强测所有有多部横向电梯的点全部超时。原因时存储横向电梯采用HashMap存储,key为楼层,当新的电梯加入时会把原来电梯覆盖而导致原来电梯无法结束线程。解决方法是用ArrayList存储横向电梯,同时在策略上让横向电梯也采取自由竞争的方式即可。

三.心得体会

线程安全

为避免线程不安全等问题,需要在设计前明确所有的共享对象、访问共享对象的线程。当存在多个共享对象和多个线程时,要考虑多个线程访问共享对象的顺序,避免出现死锁。

层次化设计

应尽可能做到每个类的功能单一化,各司其职。当修改bug时和迭代过程中,仅修改类内部的功能,不修改对外部的接口,不影响到其他类。