文章目录
一、三次作业
1.1 hw5
1.1.1 UML类图
-
本次作业中,我借鉴了OO上机时的代码架构,设计了3个线程类和1个共享类。
-
三个线程类分别为输入线程 InputThread 、调度线程 Schdeuler 、电梯线程 Elevator ,一个共享类为 RequesQueue 运行过程中, Main 开启全部的线程(包括 1 个InputThread、1 个Schdeuler 、6 个 Elevator),并定义了 6 个共享对象RequesQueue。
1.1.1 同步块设置与锁的选择
- 完成这次作业的时候其实我对同步块、锁这些概念理解的并不是特别透彻,凡是调度器中或者电梯中用到共享对象(即侯乘表)时,我都设置了很大的同步块;同时,侯乘表类中的所有方法也都加上了 synchronized 修饰词。
1.1.3 调度器设计
-
本次作业指定电梯,不需要考虑人员的分配问题。而对于电梯的运行策略,我采用的是LOOK策略。这好像没什么好多说的。
-
线程间的通信都是通过共享对象来进行的,即InputThread与Scheduler共享一个侯乘表,请求到时就由InputThread放入侯乘表,同时notifyAll()唤醒等待着的Scheduler,Scheduler将请求从侯乘表中取走。Scheduler与Elevator间的通信也是如此。线程图如下
1.2 hw6
1.2.1 UML类图
- 本次作业整体架构与第一作业相比没有比较大的变化,只不过新增了Person类以便更好处理官方标准输入包中的PersonRequest
1.2.1 同步块设置与锁的选择
- 同步块基本上没多大变化,只不过考虑到ResetRequest,电梯里的数据结构从原先的一个侯乘表对象与乘客的ArrayList,变为了一个侯乘表对象、一个Receive后的还未上电梯的乘客ArrayList,和在电梯上的乘客ArrayList。这样反而导致电梯中的同步块减少,没有太多的对共享对象的读写操作。
1.2.3 调度器设计
-
我加入了随机神教。嘿嘿。
-
又由于我让吐出去的乘客由原先的电梯接受,所以线程图没有基本没变。
1.3 hw7
1.3.1 UML类图
-
本次新增了三个类,分别是:
-
单例模式的Counter类,用来判断进程结束条件
-
共享对象的Flag类,用来确保双轿厢电梯不同时进入换乘层
-
线程DoubleElevator类,用来满足双轿厢电梯需求
-
1.3.2 同步块设置与锁的选择
- 由于电梯间的换乘会导致无法简单地通过InputThread的结束来判断是否该setEnd(),我运用了单例模式的计数器(具体实现参考了OS课程中的信号量的思路)来判断是否需要结束。不得不说,单例模式实在是非常适合多线程的应用场景,使用时不需要考虑线程安全的问题。
public class Counter {
private static Counter instance = null;
private int cnt;
public synchronized void release() {
cnt++;
notifyAll();
}
public synchronized void acquire() {
while (true) {
if (cnt > 0) {
cnt--;
break;
}
else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static Counter getInstance() {
if (instance == null) {
instance = new Counter();
}
return instance;
}
}
- 至于如何实现双轿厢电梯互斥问题,我学习了讨论区中的写法,用一个Flag的对象来进行判断。这非常地简便
public class Flag {
enum State {
OCCUPIED,
UNOCCUPIED
}
private State state;
public Flag() {
this.state = State.UNOCCUPIED;
}
public synchronized void setOccupied() {
waitRelease();
state = State.OCCUPIED;
notify();
}
public synchronized void setRelease() {
this.state = State.UNOCCUPIED;
notify();
}
private synchronized void waitRelease() {
notify();
while (state == State.OCCUPIED) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
1.3.3 调度器设计
- 碰到双轿厢的重置信号是,我会直接在原先的电梯中启动两个双轿厢电梯线程,并让该电梯吐出乘客后结束。两个新的双轿厢则共享原先的侯乘表对象。
二、迭代总结
-
由于架构的简单直接,这三次迭代我还是完成的比较顺利,期间也没有遇到多线程场景下容易发生的线程安全问题。但这种粗暴的设计也导致我损失了一定的性能分。还是挺令我感到遗憾的。
-
但总结看来,Random大法在可拓展性极高的优势。同时,它不需要调度器知晓电梯自身的状态,避免了线程间的过多耦合以及这可能导致的线程不安全问题。在客户需求没那么严苛的情境下,这不失为一种好的选择。
三、bug与debug
-
在第七次作业中,我由于未考虑到两个双轿厢共享侯乘表时,双方的修改都会导致另一方可能被唤醒。假设有A电梯不需要移动,但是B电梯收到了大量乘客需要移动,这就会导致A电梯被不断地唤醒,产生一定程度上的轮询。最终这导致我CTLE。不过由于这并不会轮询太多次,我在后面加了个sleep就应付了bug修复。
-
至于多线程debug方面,轮询的问题可以通过print来找到,但其他bug我感觉就需要通过数据生成与比对来处理了,我也是并没有发现什么特别有效的方法。
四、心得体会
- 在这三次迭代中,虽然遇到了不少困难,但我感觉还是较好地掌握了多线程编程的能力;同时,在OS的进程与线程教学中相互印证,感觉实在是收获满满。