第二单元作业总结
第一次作业
- 本次作业,需要完成的任务为单部多线程可捎带电梯的模拟;
- 本次作业中的类以及解释:
- GetRequest (输入接受类)
- Elevator (电梯类)
- Controller (控制器)
- Floor (容器)
- Building (容器)
- 主要方法时序图以及解释
- 本次设计的总体架构是基于“生产者-消费者模式”的,在第一次作业中电梯调度的基本策略是:控制器只负责电梯的开始与唤醒,具体的请求由电梯自己抓获,电梯的结束,行走路径等与控制器无关
- Controller
控制器的逻辑为反复咨询容器中有没有请求存在,如果有请求且电梯不在运行,则唤醒电梯并给予第一次电梯运行的目的地,之后将电梯控制权完全交给电梯。如果没有的话则咨询输入类是否输入结束,如果结束则标记,未结束则等待。 - Elevator
这张图在一定程度上显示了控制器与电梯的关系:电梯在运行过程中不会去主动请求控制器,而是直接查看容器内的请求。电梯的逻辑为反复查看容器内的请求,并选择一个合适的目的地。再沿途中,每上升一层,则执行Transfer方法,进行电梯内与电梯外请求的交换,然后电梯进行移动,直到电梯内没有请求且容器内没有请求。 - GetRequest
public class GetRequest extends Thread {
public void run() {
ElevatorInput elevatorInput = new ElevatorInput(System.in);
while (true) {
PersonRequest request = elevatorInput.nextPersonRequest();
if (request == null) {
//TODO:输入线程中止
break;
} else {
//TODO:向缓冲区中增加请求数据
}
}
try {
elevatorInput.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
抓获请求类逻辑比较简单,就是反复抓获请求直到输入结束。每抓获一个请求则加入容器中。
- 复杂度分析:由于第一次作业的架构比较简单,不予复杂度分析。但第一次作业明显我的思考不够深入,之后的扩展性不够好,导致之后第二次作业的重构。
- Bug与反思:
- 线程之间逻辑比较混乱,有许多无用的操作。
- 第一次作业的时候不清楚“轮询”,导致CTLE的Bug
第二次作业
-
本次作业,需要完成的任务为多部多线程可捎带调度电梯的模拟
-
本次作业中的类以及解释:
- GetRequest (输入接受类)
- Elevator (电梯类)
- Controller (控制器)
- Tuple(元组,盛放请求的数据类型)
-
主要方法时序图以及解释
- Elevator
与第一次作业的逻辑有了很大的改变,简化了电梯的运行逻辑
while (PostExist() | ReceiveExist()) {
transfer();
if(currentFloor != destination){ move(); }
else{ updateDestination(); }
}
电梯的移动策略:在电梯已知“电梯上已有的请求”和“未来需要处理的请求”之后,求最短路径。
可以看到,当不考虑强制在线的情况,即将输入一次性抛给电梯的时候,电梯除了第一次接受请求之外不需要与调度器有任何的交互。而强制在线也只是可以随时给电梯抛给新的请求,增添了电梯中的请求列表,此外并不需要与电梯有其他任何的交互。显然,当每个电梯的独立性越强的时候,总体的逻辑就越简单。同时,当我们设计的电梯越来越不依靠调度器进行工作时,我们就渐渐的向”高内聚低耦合“的设计原则靠拢。
- GetRequest
逻辑与代码与第一次作业没有任何改变 - Controller
与第一次作业相比,Controller不再是单独的一个线程,而是只是封装了分配方法的类。
-
需要有一个记录电梯的列表
-
需要有一个算法调度核
public class Controller {private ArrayList<Elevator> elevatorList; private String[] names = new String[]{"A", "B", "C", "D", "E"}; private int requestTime; private int elevatorNum; Controller(int num) { this.elevatorList = new ArrayList<>(); this.elevatorNum = 0; this.requestTime = 0; } public void ElevatorInitial(int num) { this.elevatorNum = num; for (int i = 0;i < num;i++) { Elevator elevator = new Elevator(names[i]); elevatorList.add(elevator); } } public void assignToElevator(PersonRequest personRequest) { Elevator elevatorPointer = elevatorList.get(requestTime % elevatorNum); elevatorPointer.addReceivePerson(personRequest); this.requestTime++; }
}
-
- 整个流程的功能性基本构造完成,接下来开始思考如何向程序添加wait(),sleep(),以及notify(),以及如何正确的结束进程。
- 电梯每次新增电梯请求的时候调用notifyAll()方法。当输入结束时将一个全局变量改变,然后再notify(),当检测到该全局变量值被更改之后break。
- 往队列中增加一个”特殊请求“,执行这个特殊请求之后才可以结束,否则wait()
- 记录全电梯系统人数,没有新增输入且入出平衡时结束
- 通过调度器控制,每次有一个线程结束(wait())的时候唤醒调度器,调度器分别判断输入是否结束,是否还有请求队列,电梯是否运行等等等选择结束或者继续运行。
而我的架构将电梯中的wait()换成了break,其实就是直接结束了这个线程。再次调用这个电梯的时候则使用start方法。这样的设计虽然不合常理,但对这单元作业而言可以很好的解决在程序最后保证所有进程全部结束。
- Elevator
第三次作业
-
本次作业,需要完成的任务为多部多线程可捎带调度电梯的模拟
-
本次作业中的类以及解释:
- GetRequest (输入接受类)
- Elevator (电梯类)
- Controller (控制器)
- Tuple(元组,盛放请求的数据类型)
-
主要方法时序图以及解释
- 本次的时序图与第二次作业没有任何改变
- 唯一改变的类是Controller类,对其派发请求以及增添活动电梯的方法进行了改造。
-
Bug与反思:
- 线程之间逻辑清楚了很多
- 在构造与运行逻辑上没有Bug(没发现Bug)
- 算法核由于大于号小于号写错了导致由最短路径选择成为了最长路径,导致在极端情况下运行时间超长。
-
复杂度分析
- 在涉及到电梯运行逻辑的算法核以及请求分配的算法核的部分复杂度高的离谱,但在其他的地方比较低。这反映出我写出来的算法一塌糊涂,以后要加强对算法的学习与思考。
可扩展性分析
- 由于这三次作业的总体架构都是基于“生产者-托盘-消费者”模式,所以这三次作业的的架构在确定后迭代变动量很小,变动主要集中在算法核。
- 我从第二次作业开始便对自己的代码进行了最简化的设计,可以说基本实现了或者说努力的向着“高内聚低耦合”的设计原则靠拢,各部分之间独立性很强,其扩展性也很高。
- 在我的设计架构上,增添电梯等行为的实现十分简单,改变电梯运行逻辑也仅仅是算法核的问题。但由于我投机取巧采用了对电梯线程直接结束的方法,如果电梯在等待时间时要求做出什么活动我的代码将会有大幅度的改动。除此之外的变动基本很容易实现。
hack策略
- 采用自动评测鸡的方式(但往往难以复现)
- 手动写一些极限数据
- 对一部电梯大量执行最复杂的极限数据
- 在时间限制的末尾增加请求
- 在第三次作业中重复增加换乘策略
- 对电梯的载人量的测试
心得体会
- 自己对于面向对象的理解有了很大的提升
- 未来可能会变得难难难难难难难难难难难难难难难难难难难难难难难难难难难难难难难难难难难难难难难难难难难难难难难难难难难难上加难
- OO就是落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落
- 自己对线程安全贯彻的不够彻底,对保护线程的其他方法理解浅薄
- 希望自己未来多多努力!