第一次作业:单部多线程傻瓜调度电梯
设计策略
本次作业我才用的是生产者消费者模式,创建一个RequestList类,将输入线程InputThread作为生产者,负责将请求放入RequestList;将电梯线程ElevatorThread作为消费者,负责从RequestList中取出请求。通过synchronize实现两个线程的互斥访问。
代码分析
-
类图
-
方法复杂度
-
类复杂度
BUG分析
此次作业在公测与互测中均未发现BUG,也没能在互测阶段hack成功。
第二次作业:单部多线程可捎带调度(ALS)电梯
设计策略
第二次作业仍然可用生产者消费者模式,总体与上次一样。调度策略也按照指导书的说明使用ALS调度。在实现上,由于每次都通过调度器发送开关门、上下乘客的信号较为麻烦,因此我通过在电梯中增加一个Passenger请求队列,用来表示在乘坐电梯的人,电梯每到一层,就检查Passenger队列,若是有人的目的楼层在当前楼层,则电梯会自动开门,然后将此人从Passenger队列中剔除。当Passenger队列为空时,通过调度器调度电梯;否则,电梯会自己运行并在每一楼层更新Passenger队列,直到Passenger队列为空停止。这样的实现方法相比电梯的所有操作都由调度器管理来说要简单许多,但这样写就相当于电梯本身就带有调度器的功能,使得这两个类的功能有所重合,这不符合面向对象的设计原则,因此并不推荐这样的写法。
时序图
代码分析
-
类图
-
方法复杂度
可见,Elevator类中有不少方法的复杂度都比较高,这也是此次设计中的不足之处。
-
类复杂度
BUG分析
此次作业中,我在线程退出的问题上存在BUG。本次作业我有三个线程,分别为电梯,调度器和输入线程,而调度器作为调度线程,理应在其他线程都已结束的情况下,再进行退出,可我的代码中,只要输入线程已经结束,那么调度器就会结束,整个程序就直接结束。这样会导致电梯中还有乘客未走出电梯,但电梯已经停止的情况。BUG的解决办法就是设置一个电梯线程的结束标志,当电梯线程输入线程都结束后,调度器才会结束。
此次作业未发现他人BUG。
第三次作业:多部多线程智能(SS)调度电梯
设计策略
仍然使用生产者消费者模式。
此次作业我共启动了六个线程。分别为三个电梯、一个输入线程、一个调度器、一个楼层检测线程。每个电梯都有属于自己的ElevatorList与TransferList,即电梯请求与换乘请求,电梯只负责从ElevatorList中取出请求并按照请求将乘客送往指定位置,当乘客到达目的地后,楼层中就会出现该乘客,将该乘客的id加入到floorPassenger中。输入线程作为生产者,读入请求并将其加到RequestList中。调度器负责从RequestList中取出请求并进行分析,若是不需要乘客换乘的请求,则直接将请求加入到对应电梯的ElevatorList;若是需要乘客换乘,则将该请求拆分为两个请求,将第一个请求加入到对应电梯的ElevatorList,将第二个请求加入到对应电梯的TransferList中。楼层检测线程,则是不停检测floorPassenger与TransferList,只要有楼层中的乘客id与TransferList中的id相匹配,则将TransferList中的请求删除,并将该请求加入到对应电梯的ElevatorList,这样就可以解决需要换乘请求的先后顺序问题。
由于此次作业我沿用了上次作业的代码,因此在实现可捎带功能的部分,设计原则依然存在与面向对象思想相违背的情况。
时序图
代码分析
-
类图
-
方法复杂度
-
类复杂度
这次作业由于要实现的功能更多,因此许多方法和类的复杂度都比较高。
BUG分析
此次作业中未发现线程调度与设计上存在的BUG。
总结
根据SOLID原则进行整体评价:
- SRP(单一责任原则):由于在二三次作业中电梯都覆盖了部分调度器功能,因此不符合。
- OCP(开放性原则) :后两次作业都沿用了之前的代码,代码可拓展性较好。符合。
- LSP(里氏替换原则) :这三次作业只继承了Thread类,电梯对象的创建是通过参数设置完成的。符合吧。
- DIP(依赖倒置原则) :符合吧?
- ISP(接口分离原则) :本单元作业只用了Runnable接口。
体会与收获
本单元重点集中在多线程上。
第一次作业,是实现一个最简单的多线程。主要是为了熟悉线程的基本操作,例如线程的创建、结束等,只要了解synchronize关键字的用法,很容易就完成此次作业。
第二次作业,本质上与第一次作业没有太多区别,但在功能实现上更加复杂,而且严格限制了CPU_TIME_LIMIT与REAL_TIME_LIMIT。因此,此次作业对于我们在线程调度上有一定要求,不能再像第一次作业那样暴力轮训,必须通过wait与notify等操作实现线程调度,否则就会出现TLE。此外,由于捎带功能的出现,导致代码更加复杂,若是出现BUG,也要求我们具有有一定的多线程调试能力。
第三次作业,才是真正的综合实践:多部电梯,各个电梯可达楼层不同,速度也不同,可承载人数也不同,需要乘客等待、换乘,不再如同前两次作业般的傻瓜调度。要完成此次作业难度不是特别高,但想要真正优化好,就需要有良好的调度策略:调度器需要根据电梯内人数按照一定优先级分配请求,必要时还要让不用换乘的乘客实行换乘提高效率的目的。
总的来说,经过本单元的锻炼,在多线程设计方面一定会有很大的提升。
期待下个单元的主题。