Loading

北航面向对象设计与构造2021第二单元作业总结

实拍电梯

本单元任务是用多线程模拟电梯。在依次回答所要求回答的问题之前,先放几张研讨会分享PPT的截图,使读者对我的设计有大致的了解。

我的设计

不显式使用synchronized

算法

为每层创建优先阻塞队列

  1. 总结分析三次作业中同步块的设置和锁的选择,并分析锁与同步块中处理语句直接的关系。

    答:由于使用了java.util.concurrent库所提供的实现BlockingQueue<T>接口的容器,三次作业中均未显式设置同步块和锁。调度器(生产者)使用了自动阻塞的put()方法向PriorityBlockingQueue中放乘客,效果是由于容器中ReentrantLock的存在,PriorityBlockingQueue内部被锁时,调度器将在put()时被阻塞。电梯(消费者)可以使用自动阻塞的take()方法从PriorityBlockingQueue中取乘客,效果是由于容器中ReentrantLock的存在,PriorityBlockingQueue内部被锁时,电梯将在take()时被阻塞。由于电梯大部分时间处于繁忙状态,我的设计中电梯并未使用自动阻塞的take()方法,而是使用了取不到就返回nullpoll()方法,这样电梯在某层取不到乘客时不必等待,而是可以继续运送已取到的乘客,提高效率。

  2. 总结分析三次作业中的调度器设计,并分析调度器如何与程序中的线程进行交互。

    答:三次作业中调度器的职责基本没有变化,均只负责读取输入数据,往候乘表中放乘客,通知电梯开始/结束工作。

    为什么调度器要通知电梯结束工作?这是因为,如果候乘表不用BlockingQueue而用普通容器+synchronized同步块时,不会再有新乘客到来时,调度器需要调用一次notifyAll()告知电梯;而使用封装好的BlockingQueue时,没有notifyAll()这样的机制,不会再有新乘客到来时,调度器只能向候乘表末添加一个 Dummy Passenger 代表结束,当电梯获取到这个 Dummy Passenger 就可以结束工作了。需要注意的是,如果BlockingQueue为空,只能得知当前没有乘客到来,不能得知不会再有乘客到来了。就像货架空了,你只能知道现在买不到货了,不能知道是未来还会补货、还是未来再也不卖了。

    但在我的设计中每层都有候乘表,由于结束时电梯的位置并不确定,需要往每层的候乘表中都放一个Dummy Passenger,我觉得这种方法并不是很优雅,于是为每个电梯建立了一个LinkedBlockingQueue作为开始/结束工作的命令。由于LinkedBlockingQueuetakeLockputLock两把锁,因此支持“同时”读写,如T1-T2-T1-T2-T1-T2-T1-T2-T1-T2......-T1-T2,比只有一把锁、不支持“同时”读写的ArrayBlockingQueue效率更高,如T1-T2-[T1-T1-T1-T1-...-T1]-[T2-T2-...-T2]-T2-T1

    LinkedBlockingQueueJDK 源码截图:

    LinkedBlockingQueue

    ArrayBlockingQueueJDK 源码截图:

    ArrayBlockingQueue
  3. 从功能设计与性能设计的平衡方面,分析和总结自己第三次作业架构设计的可扩展性。

    答:图见下方。功能设计上,满足基本功能;性能设计上,满足基本性能要求。由于未针对不同调度策略做类的派生、继承等,扩展性上略显不足。

  4. 分析自己程序的 bug。

    答:由于使用了java.util.concurrent库所提供的实现BlockingQueue<T>接口的容器,bug 均与线程安全无关,集中在程序设计的基础控制流(分支跳转条件)、边界值(>=还是><=还是<)。这些错误过于低级,写在这里太丢脸,今后再忙写程序也要头脑清醒,否则浪费大量时间去de很低级的bug很不值,令人懊恼qaq。

  5. 分析自己发现别人程序 bug 所采用的策略。

    答:由于本月冯如杯 DDL 如同催命,实在没精力去 hack 别人的 bug。我知道和线程安全有关的 bug 不易复现,所以若要 hack,除了分析对方的代码,还需写一个高并发的评测机。

  6. 心得体会。

    答:通过这次作业,我对线程安全和层次化设计有了更深入的了解和认识,相关能力得到了提高。我从中得到了确定:不重复造轮子是很有必要的,我的选择是正确的。此外,也警示我要头脑清醒、避免低级错误。

ArrayList<PriorityBlockingQueue<Passenger>> waitQueues int decideOriginFloorFrom(int, String) Passenger pollAt(int) void put(int, Passenger) boolean thereArePassengersAt(int) boolean empty WaitQueues START_WORKING SHUT_DOWN ElevatorCommand valueOf(String) ElevatorCommand[] values() ElevatorCommand int maxCommandCount WaitQueues waitQueues HashMap<String, LinkedBlockingQueue<ElevatorCommand>> elevatorCommands String arrivingMode ElevatorInput requestInput void enableElevator(String) void run() Scheduler int compareTo(Passenger) boolean equals(Object) int hashCode() int destination int id int origin Passenger boolean DEBUG int MAX_CMD_CNT int HIGHEST_FLOOR int LOWEST_FLOOR void main(String[]) Main String id int capacity String arrivingMode LinkedBlockingQueue<ElevatorCommand> command WaitQueues waitQueues int currentFloor LinkedList<Passenger> passengers int changeTargetFloorByPassengers() int changeTargetFloorByWaitQueue() void closeTheDoor() void moveAndArrive() void moveToNearestFloor(int) void openTheDoor() void passengersGetOff() void passengersGetOn() void run() void startWorking() boolean thereArePassengersToGetOn() Elevator 1 1 * 1 1 1 «create» «create» * 1 «create» * 1 * 1 «create» ElevatorInput ElevatorInput Main Main Scheduler Scheduler WaitQueues WaitQueues ElevatorCommands ElevatorCommands OriginalElevator OriginalElevator NewElevator NewElevator Initialize run run Enable Elevator enableElevator run Put Passenger Request nextRequest put Poll Passenger Request take loop [StartWorking] loop [ChangingTargetFloor] poll ChangeTargetFloor [DoorOpening] openTheDoor passengersGetOff [DoorClosing] passengersGetOn closeTheDoor Input Finish null Shut Down take take
posted @ 2021-04-27 21:01  人生就像一盘棋  阅读(72)  评论(0编辑  收藏  举报