main初始化时间戳,创建大楼,并传入输入线程的创建方法中,启动输入线程。Floor类型的变量,放入ArrayList中。Floor又会针对三类电梯上下行创建6个Queue类型对象,用于存放新进入的乘客。inputMutex.notifyAll()唤醒所有的调度器线程,之后根据请求的类型创建电梯或将乘客传递给大楼。mode参数创建特定电梯。Floor对象,再由楼层对象根据其到达地点等信息分配到给定队列中。assignMutex的等待池中,在被唤醒后,获得乘客请求。前一步是获得有效的目标楼层,接着判断,如果目标楼层是-1,表明电梯进程被中止,于是设置电梯stopped为真,在后续运行代码中跳出死循环,从而结束线程。其他目标楼层都可以认为有效,于是通过moveUp()或moveDown()方法到达相应楼层,再查看当前方向的同方向队列是否为空,如果为空,说明电梯需要改变方向。inNeed()对该楼层是否停下进行判断(携带乘客的目的地,或负载未满时该层有同方向的乘客请求),如果停下,则执行open()、out()、in()和close()这一系列行为;否则按照既定的方向继续运行。null输入后,将调度器stopped位设置为真,表示处理完当前的乘客后即可停止,并使用inputMutex唤起所有调度器线程。assignMutex唤起电梯线程,并结束自身的线程。stopped位设置为真,并在assign()后判断停止位,如为真,则退出死循环,从而结束线程。在Input线程和Elevator线程的run()方法中,产生或处理新的PersonRequest 的区域设置了同步块,以管理PersonRequest的对象building为锁。但是这样设计导致某一楼层生成新需求,或是电梯在处理某一楼层的需求时,整栋楼都不能被其他线程操作,导致效率非常低下。而且也使得类Building里分层管理PersonRequest的队列几乎没有发挥作用。
构造了一个线程安全类Queue专门用于管理PersonRequest,其中的方法都处于临界区中,锁为队列自身。大楼Building类管理了所有楼层Floor,而楼层Floor类管理着三种电梯分别的排队队列Queue。同时,电梯Elv自身拥有一个队列Queue类用来记录已经进入电梯的乘客。
除此以外,还使用了inputMutex和assignMutex,分别用于处理输入线程和调度器线程,调度器线程和电梯线程之间的通信,以使得调度器线程、电梯线程可以获知输入中止的状况,从而产生相应的行为来响应。
在这两次作业中,调度器并没有作为单独的线程运行,而只是作为类Elevator中一个成员对电梯状态进行管理。在接到输入线程传来的模式后,电梯工厂会创建相应的调度器,并在创建电梯线程时将调度器传入其中。每台电梯各自有一个调度器,当电梯要使用调度器的时候,将状态类EState和管理所有等待乘客的类Building传入调度器,调度器根据其模式特点返回一个新的状态信息,接着电梯就根据新的状态信息执行相应的行为动作。
几个状态包括:
0,可运行的状态,这个时候电梯停在某一楼层,暂时没有接收到新指令,且此时输入未停止。
1,移动的状态,这个时候电梯需要前往某一特定楼层。
2,开门的状态,这个时候电梯停在某一楼层并开启了电梯门。
3,关门的状态,这个时候电梯刚刚关上门。
-1,中止的状态,此时输入停止且已有的等待乘客已运载完毕。
通过setAvailable(),isAvailable()等方法,隐藏了状态的具体信息。这些状态间的转换一部分是由电梯自身执行的:
在这次作业中,调度器作为单独的线程运行。
调度器与输入线程间通过inputMutex通信。在当前等待乘客全部运载完毕后,调度器进入等待的状态,只有在输入线程获取新输入时才会被唤醒,从而继续运行。
与电梯通过assignMutex来通信。调度器获知有新输入后,通过这一信号量唤醒等待的电梯线程。

第三次作业能基本保证运行的正确性,将乘客接送到特定楼层,并在输入结束后结束程序。但是也有许多还可以优化的地方。
电梯不能实现多部电梯的换乘。在这次作业中,每层楼都采取了针对不同电梯类型、上下行划分的六个队列,调度器在查找可用队列时较为方便,争抢只存在于同类型的电梯之间。然而这导致性能大幅下降,A类电梯承担了大部分运载任务,而另两部电梯长期处于空闲状态。
另外,电梯虽然接受了运行模式的参数,但没有相应的调度器针对不同情况对电梯进行调度。
暂时没有分析出bug,在这方面的分析能力还需提升。
线程安全的关键在于共享对象的管理。通过构建线程安全类使得线程代码中对这些对象的访问更为简便可靠。
理论上所有代码可以放在一个文件里,但是这样变得分析和修改,通过层次化设计,编写时可以只注重当下这一层次的内容。
我觉得层次化设计可以从两个视角来理解。
一个视角是整个问题,在这个视角下,问题被拆分为诸多对象。在本单元的作业中,问题中的对象有实际的大楼,用于存放数据内容(乘客),也有输入线程、调度器、电梯这类处理数据的对象。存放数据的对象的方法主要是针对数据的查询、移除和增加等功能,同时,为了其他方法调用方便,也有查询是否为空的方法等等。处理数据的对象则是在此基础上对前述对象进行管理。合适的拆分方式有助于编程顺利进行。在第二次作业中,因为拆分不佳,导致一些对象需要在多个方法间传递,其功能也没有得到很好的封装,使得自行调试和修改非常困难。
另一个视角是类里的private方法,将对象的一系列特定行为(这些行为是必定连续发生的)整合为一组方法,在减少重复代码的同时,有助于提高代码可读性。比如电梯类里的开门、关门和运行等等写为private方法时,在run中调用方法名,整个逻辑流程就显得一目了然。
最初两次作业完成情况不佳,很大一部分原因是在“设计”阶段花费了过多的时间,有些设计思路是生搬硬套上去的,自己也不知道为什么要这样设计。在第三次作业终于通过了中测后,我认为学习代码设计这方面的内容最重要的是动手开始实践,而不要担心初期设计得不好就一直不开始,也不要害怕犯错误、重构。在实践的过程中,我收获最多的其实是试错。
在尝试了轮询(当时写的时候我还不理解轮询的意思)所有测试点都CPU超时后,我才深刻地体会到wait-notify机制的作用。因为第二次的调度器设计几乎无法正常工作,在第三次作业中我必须重构代码,但是在重构的过程中,我感到我的思路比第二次更为清晰。在我目前编程能力不足的情况下,重构几乎难以避免,甚至可以说就是我学习的一部分。
原文:https://www.cnblogs.com/19373639-qiu/p/2021_OO-Unit2_Summary-19373639.html