首页 > 其他 > 详细

第二单元总结

时间:2021-04-27 19:52:54      阅读:15      评论:0      收藏:0      [点我收藏+]

设计策略

第一次和第二次策略

  1. 在获取输入模式后,传入电梯工厂,创建对应的调度器,并使之作为参数传递到新创建的电梯里。
  2. 电梯首先调用调度器,调度器返回新状态,电梯按照新状态运行到相应楼层,使所有与当前同方向的乘客都进入电梯,并按照距离远近顺序选定下一目的地。
  3. 调度器根据不同模式有不同的返回值,但在第二次作业中,因为架构问题实际上都没有实际实现。

第三次策略

初始化

  1. main初始化时间戳,创建大楼,并传入输入线程的创建方法中,启动输入线程。
  2. 大楼针对每一层创建Floor类型的变量,放入ArrayList中。Floor又会针对三类电梯上下行创建6个Queue类型对象,用于存放新进入的乘客。

运行

  1. 输入线程启动后,在每次输入后,使用inputMutex.notifyAll()唤醒所有的调度器线程,之后根据请求的类型创建电梯或将乘客传递给大楼。
  2. 电梯工厂会响应创建电梯的请求,并使用已保存的mode参数创建特定电梯。
  3. 大楼接收到一个乘客请求后,首先会根据其出发楼层分配给对应的Floor对象,再由楼层对象根据其到达地点等信息分配到给定队列中。
  4. 电梯在运行前调用调度器分配目标楼层。如果分配的目标楼层是0,代表暂时没有新的乘客请求,电梯于是进入assignMutex的等待池中,在被唤醒后,获得乘客请求。前一步是获得有效的目标楼层,接着判断,如果目标楼层是-1,表明电梯进程被中止,于是设置电梯stopped为真,在后续运行代码中跳出死循环,从而结束线程。其他目标楼层都可以认为有效,于是通过moveUp()moveDown()方法到达相应楼层,再查看当前方向的同方向队列是否为空,如果为空,说明电梯需要改变方向。
  5. 电梯如果在分配阶段没有被中止,则只要携带乘客数量不为零,则一直运行下去。在运行过程中,每到一层楼,电梯通过inNeed()对该楼层是否停下进行判断(携带乘客的目的地,或负载未满时该层有同方向的乘客请求),如果停下,则执行open()out()in()close()这一系列行为;否则按照既定的方向继续运行。
  6. 电梯运行期结束后,则再次返回之前的位置,调用调度器请求分配楼层。

中止

  1. 在输入线程遇到null输入后,将调度器stopped位设置为真,表示处理完当前的乘客后即可停止,并使用inputMutex唤起所有调度器线程。
  2. 调度器线程在停止位设置为真后,在相应类型的队列全为空时的返回楼层值为-1(相比之下,如果调度器停止位为假时,如果相应队列全为空会返回0),并通过assignMutex唤起电梯线程,并结束自身的线程。
  3. 电梯收到-1的目标楼层后,将自身stopped位设置为真,并在assign()后判断停止位,如为真,则退出死循环,从而结束线程。
  4. 如此程序所有线程都停止了,且所有请求处理完毕。

同步块与锁

第一次和第二次作业

Input线程和Elevator线程的run()方法中,产生或处理新的PersonRequest 的区域设置了同步块,以管理PersonRequest的对象building为锁。但是这样设计导致某一楼层生成新需求,或是电梯在处理某一楼层的需求时,整栋楼都不能被其他线程操作,导致效率非常低下。而且也使得类Building里分层管理PersonRequest的队列几乎没有发挥作用。

第三次作业

构造了一个线程安全类Queue专门用于管理PersonRequest,其中的方法都处于临界区中,锁为队列自身。大楼Building类管理了所有楼层Floor,而楼层Floor类管理着三种电梯分别的排队队列Queue。同时,电梯Elv自身拥有一个队列Queue类用来记录已经进入电梯的乘客。
除此以外,还使用了inputMutexassignMutex,分别用于处理输入线程和调度器线程,调度器线程和电梯线程之间的通信,以使得调度器线程、电梯线程可以获知输入中止的状况,从而产生相应的行为来响应。

调度器设计

第一次和第二次作业

在这两次作业中,调度器并没有作为单独的线程运行,而只是作为类Elevator中一个成员对电梯状态进行管理。在接到输入线程传来的模式后,电梯工厂会创建相应的调度器,并在创建电梯线程时将调度器传入其中。每台电梯各自有一个调度器,当电梯要使用调度器的时候,将状态类EState和管理所有等待乘客的类Building传入调度器,调度器根据其模式特点返回一个新的状态信息,接着电梯就根据新的状态信息执行相应的行为动作。
几个状态包括:
0,可运行的状态,这个时候电梯停在某一楼层,暂时没有接收到新指令,且此时输入未停止。
1,移动的状态,这个时候电梯需要前往某一特定楼层。
2,开门的状态,这个时候电梯停在某一楼层并开启了电梯门。
3,关门的状态,这个时候电梯刚刚关上门。
-1,中止的状态,此时输入停止且已有的等待乘客已运载完毕。
通过setAvailable()isAvailable()等方法,隐藏了状态的具体信息。这些状态间的转换一部分是由电梯自身执行的:

  1. 由移动到开门;
  2. 由开门到关门。
    另一些则通过调度器根据相应楼层信息更新状态:
  3. 由可运行到移动/开门(如果停留的楼层就有新的等待乘客,那么就要直接开门);
  4. 由关门到可运行/移动/中止。
    因为调度器既需要知道各个楼层信息,又需要知道电梯状态信息,与此同时还处在电梯类内部,实际上除了使得电梯表面上的代码量减少,作用其实不大。除此以外,电梯状态的维护由多个类里的方法进行,也不便于状态类的修改。最终导致第三次作业时不得不重构代码。

第三次作业

在这次作业中,调度器作为单独的线程运行。
调度器与输入线程间通过inputMutex通信。在当前等待乘客全部运载完毕后,调度器进入等待的状态,只有在输入线程获取新输入时才会被唤醒,从而继续运行。
与电梯通过assignMutex来通信。调度器获知有新输入后,通过这一信号量唤醒等待的电梯线程。

功能设计与性能平衡

UML类图

技术分享图片

功能设计与性能平衡

第三次作业能基本保证运行的正确性,将乘客接送到特定楼层,并在输入结束后结束程序。但是也有许多还可以优化的地方。
电梯不能实现多部电梯的换乘。在这次作业中,每层楼都采取了针对不同电梯类型、上下行划分的六个队列,调度器在查找可用队列时较为方便,争抢只存在于同类型的电梯之间。然而这导致性能大幅下降,A类电梯承担了大部分运载任务,而另两部电梯长期处于空闲状态。
另外,电梯虽然接受了运行模式的参数,但没有相应的调度器针对不同情况对电梯进行调度。

bug分析

暂时没有分析出bug,在这方面的分析能力还需提升。

心得体会

线程安全

线程安全的关键在于共享对象的管理。通过构建线程安全类使得线程代码中对这些对象的访问更为简便可靠。

层次化设计

理论上所有代码可以放在一个文件里,但是这样变得分析和修改,通过层次化设计,编写时可以只注重当下这一层次的内容。
我觉得层次化设计可以从两个视角来理解。
一个视角是整个问题,在这个视角下,问题被拆分为诸多对象。在本单元的作业中,问题中的对象有实际的大楼,用于存放数据内容(乘客),也有输入线程、调度器、电梯这类处理数据的对象。存放数据的对象的方法主要是针对数据的查询、移除和增加等功能,同时,为了其他方法调用方便,也有查询是否为空的方法等等。处理数据的对象则是在此基础上对前述对象进行管理。合适的拆分方式有助于编程顺利进行。在第二次作业中,因为拆分不佳,导致一些对象需要在多个方法间传递,其功能也没有得到很好的封装,使得自行调试和修改非常困难。
另一个视角是类里的private方法,将对象的一系列特定行为(这些行为是必定连续发生的)整合为一组方法,在减少重复代码的同时,有助于提高代码可读性。比如电梯类里的开门、关门和运行等等写为private方法时,在run中调用方法名,整个逻辑流程就显得一目了然。

其他

最初两次作业完成情况不佳,很大一部分原因是在“设计”阶段花费了过多的时间,有些设计思路是生搬硬套上去的,自己也不知道为什么要这样设计。在第三次作业终于通过了中测后,我认为学习代码设计这方面的内容最重要的是动手开始实践,而不要担心初期设计得不好就一直不开始,也不要害怕犯错误、重构。在实践的过程中,我收获最多的其实是试错。
在尝试了轮询(当时写的时候我还不理解轮询的意思)所有测试点都CPU超时后,我才深刻地体会到wait-notify机制的作用。因为第二次的调度器设计几乎无法正常工作,在第三次作业中我必须重构代码,但是在重构的过程中,我感到我的思路比第二次更为清晰。在我目前编程能力不足的情况下,重构几乎难以避免,甚至可以说就是我学习的一部分。

第二单元总结

原文:https://www.cnblogs.com/19373639-qiu/p/2021_OO-Unit2_Summary-19373639.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!