OO - 第二单元总结 - 博客作业- 79066020 路凯
单元题目介绍和说明
第二单元的主要关注点是线程和它们之间的共享数据,我们在3次作业任务中的主要目标是实现一个电梯系统,每次作业都有不同的要求,使用线程来模拟电梯实时运行。在第一次作业中,我们被介绍到以下要求:总共6部电梯,从1楼到11楼,最多搭载6名乘客。
乘客的请求输入通过课程提供的官方包接收,我们接收到以下信息:乘客ID(保证每个乘客获得唯一的ID)、乘客的位置和目的楼层。我们可以自由实现任何算法来处理乘客请求,但用于测试的是ALS算法作为标准。
我们还接收到一些其他要求值,如门开启时间、楼层行驶时间、起始楼层等。
第一个作业
由于我们知道每个单元都有三次作业任务,第一次作业任务是后两次的基础,我试图规划我的代码尽可能模块化,这样我就不需要在接下来的两次作业中改变太多的代码和结构,只需要实现新的要求并适应我已经拥有的代码。不幸的是,我遇到了一些问题,并且对最终的代码结构不太满意,所以在提交第一版本之后,在第二次作业任务之前,我重新规划并重新从头开始编写了代码。这里我将解释我的代码的两个版本及其差异。
这个单元的第一次作业比较简单,除了请求中的乘客信息外,我们还接收到它将乘坐的电梯id,这意味着我们的调度逻辑和算法只需要解决何时接送乘客以及首先接送谁。
架构分析
1.0架构
根据上图的UML图,我们可以看到系统围绕一些关键组件构建:Elevator(Extends thread),Elevator Control(Extends thread),Passenger。ElevatorControl使用管理类来处理多个乘客和电梯。算法封装在一个算法类中,所以如果我们有时间更改和改进它,可以很容易地完成。
代码中的关键类是ElevatorControl,这是调度类,这是将在接下来的作业任务中发生主要变化的类。对于调度,我的方法最初是直截了当的,专注于按照它们到来的顺序完成请求,我忽略了一个事实,即Elevator线程不应该继续运行并检查新请求,所以第一次提交作业时我遇到了CPU_TIME_EXCEDED错误。意识到我的错误后,我随后更改了代码,保持在ElevatorControl中跟踪请求并根据它们接收的请求唤醒Elevator线程。
对于算法,ElevatorMovementAlgorithm根据电梯的当前状态和等待乘客的队列决定下一个动作。这个算法决定电梯应该向上移动、向下移动、开门或保持静止。这个逻辑确保了电梯以一种优先考虑即时动作的方式运行,比如在正确的楼层开门,同时也考虑了未来移动的方向,以高效地达到下一组乘客。
public ElevatorAction decideNextAction(Elevator elevator, Queue<Passenger> waitingQueueForElevator) {
TreeSet<Integer> stops = new TreeSet<>(elevator.getStops());
if (stops.contains(elevator.getCurrentFloor()) && elevator.getElevatorState() != ElevatorState.OPENING_DOORS && elevator.getElevatorState() != ElevatorState.CLOSING_DOORS) {
return ElevatorAction.OPEN_DOORS;
}
if (!stops.isEmpty()) {
if (stops.higher(elevator.getCurrentFloor()) != null) {
return ElevatorAction.MOVE_UP;
} else if (stops.lower(elevator.getCurrentFloor()) != null) {
return ElevatorAction.MOVE_DOWN;
}
}
return ElevatorAction.IDLE;
}
除了CPU_TIME_EXCEDED错误,我在处理ctrlD通知不会有更多请求时也遇到了一些问题,我的代码要么输出一个异常,要么中断电梯在楼层之间移动以及开关门所花费的时间。为了解决这个问题,我不得不多次重构我的代码,它开始变得非常混乱和杂乱,我对PassengerManager也不是很满意,它感觉不是很有用,我认为在第一次学习原理后最好重新从头开始编写。
2.0架构
对于这个更新的代码,我摒弃了PassengerManager,开始在ElevatorControl内部处理请求,而不是在Main类中,我增加了ElevatorControl类的重要性,这在下一个作业中将非常有用。
public void run() {
try {
while (true) {
synchronized (this) {
while (waitingQueueForElevator.isEmpty() && insidePassengers.isEmpty() && !noMoreRequests) {
wait();
}
if (noMoreRequests && waitingQueueForElevator.isEmpty() && insidePassengers.isEmpty()) {
break;
}
}
checkWaitingQueue();
ElevatorAction action = elevatorMovementAlgorithm.decideNextAction(this, waitingQueueForElevator);
switch (action) {
case MOVE_UP:
moveUp();
break;
case MOVE_DOWN:
moveDown();
break;
case OPEN_DOORS:
openDoor();
closeDoor();
reevaluateStops();
break;
case IDLE:
break;
}
}
} catch (InterruptedException e) {
if (waitingQueueForElevator.isEmpty() && insidePassengers.isEmpty()) {
Thread.currentThread().interrupt();
}
}
}
第二个作业
对于第二次作业,我们基本上有两个新要求:第一个是现在请求不再有ElevatorID编号,它们可以由任何电梯执行。第二个是我们现在也必须实现电梯的RESET请求:对于这些请求,电梯应该立即放下所有乘客,并且在RESET期间,电梯的容量和行驶时间可以改变。
架构分析
为了完全实现新的要求,我们必须对ElevatorControl和电梯结构进行一些更改,现在我们还需要一个调度算法来将乘客分配给电梯,我们通过实现一个调度算法接口并在NearestElevatorFirst算法中实现它来实现这一点,这考虑了电梯的资格(例如,它是否正在重置或已满)之前分配它们。另一个重大变化是,现在还需要一个队列来安排正在调度的乘客,这个列表不仅保存了新来的乘客,还保存了在重置期间被电梯放下的乘客,他们需要被重新分配给其他电梯。
在这次作业中,我们的主要目标是实现良好的调度技术,同时仍然保证线程安全,因为在这次作业中,电梯在RESET期间将需要访问ElevatorControl等待队列以放下乘客。当电梯重置时,里面的所有乘客和那些分配给它的乘客都会被安全地重新定向回由ElevatorControl管理的全局等待队列,以便重新分配。这个重新分配过程是同步的,以防止竞态条件,确保在转换期间没有乘客丢失或错误分配,为此我使用了synchronized块和像ConcurrentLinkedQueue这样的并发数据结构来管理电梯内的乘客和等待队列。这确保了添加或移除队列中的乘客以及更新电梯的当前楼层和状态等操作是原子和线程安全的。此外,对shutdownRequested标志使用volatile保证了跨线程的可见性,确保所有线程正确观察到关闭状态。
第三个作业
关于第三次作业,主要难点在于电梯系统的复杂性显著增加。这次的要求是六部电梯每部都被进一步分为两个单独的实体,分为电梯A和电梯B。这一调整就需要全面重新评估电梯调度过程以适应电梯数量的增加。此外,这一变化在管理线程间共享数据方面引入了更多的复杂性,除了确保高效的调度和最佳的乘客服务之外,还出现了一个新的关键要求,就是需要确保电梯不会相互碰撞。这不仅需要复杂的调度逻辑,还需要精密的协调机制来保证电梯安全。
但是由于时间限制和系统复杂性的突然增加,我发现自己在规划和实现有效解决方案方面遇到了很大的困难。设想构建一个智能管理更多电梯、确保线程安全访问共享资源并且防止出现电梯碰撞的新调度算法,需要很长的时间的。尽管我努力适应这些要求去应对带挑战,我还是无法分配足够的时间来开发满足这些严格标准来实现构想解决方案。我很遗憾地无法提交第三次作业,经过这次的失败,我更能体会在软件开发中强大的时间管理和战略规划的重要性,特别是面对超出预期的复杂性的情况下。
总结和收获
通过这一系列的作业,我对Java面向对象设计有了深刻的了解,学会了如何组织我的类,当然也包括了关于线程和线程安全的知识。作为一名留学生,我在注意代码要求的某些细节上存在一些困难,而完成所有这些要求最大的难题无疑是时间,尤其是在单元的最后一次作业中。这些经历不仅加深了我对Java编程的理解,也让我认识到了在面对复杂系统设计时高效时间管理和精确规划的重要性。我学会了如何在紧迫的时间内合理分配我的精力,以及如何在多线程环境中维护数据的完整性和安全性。尽管面临挑战,但通过不断尝试和学习,我感到自己在解决问题和开发复杂系统方面有了显著进步。这次经历不仅提高了我的技术能力,也锻炼了我的适应能力和解决问题的能力。