BUAA_OO_第二单元作业总结
BUAA_OO_第二单元作业总结
简述
第二单元学习的主要是多线程,在多线程的作业中,我主要采取了生产者-消费者的设计模式,在这种模式下,我能够尽可能地避免由于多线程造成的数据安全问题,较为简单地保护共享对象的数据安全。
第一次作业
由于是第一次的作业,因此相当于后两次的作业来说,难度并不是很难。
在这次的作业中,我主要采用了SRP的设计原则,通过Elevator
这一个类来实现电梯的所有功能。同时,对于线程安全的问题,我按照生产者-消费者策略,将PersonRequest
封装成了RequestQueue
类,并对其PersonRequest
的取出和存入的方法进行了synchronized
的保护。
对于调度策略来说,主要采用的还是ALS调度策略。
UML图
BUG分析
在这一次的作业中,我最大的BUG在于没有对线程的输出安全进行保护,因此,我新建了一个静态类TimeOut
对输出方法进行安全保护。
同时,在后续的分析过程中,我发现在这一次的作业中,我有很多的过程是计较冗余的,比如Schedule
线程和InputThread
线程,事实上这两个线程功能是比较冗余的,为此我在后一次的作业中进行了优化。
第二次作业
相较于上一次的作业来说,这一次的作业难度还是上升了不少。
这次作业新增了一种横向电梯,相较于第一次作业中的纵向电梯来说,其主要是在不同楼座间进行移动。因此,我主要采用了OCP的设计策略,建立了ElevatorBB
类(横向电梯)和ElevatorFF
类(纵向电梯)两个子类来对Elevator
这个父类进行继承,进而方便后续的扩展。
在这次作业中,由于我们的乘客一次只能在同一层楼或者同一座楼移动,所以我们不需要考虑到乘客的换乘,因此,我主要还是采取了第一次的调度策略,在有多部电梯选择时,为求稳定,我主要采用了指导书中较为均衡的调度方式。
同时,在这一次的作业设计中,我对上一次作业中一些比较冗余的部分也进行了简化。同时,我的程序是每一个电梯配一个WaitingQueue
,因此,在乘客请求输入时,Schedule
线程便对请求进行了分配,从而减少了乘客等待的时间。
UML图
第三次作业
其实这一次的作业最主要的变化就是增加了换乘的要求,而我的做法是将横向电梯与纵向电梯进行关联,并使得电梯之间能够互相调度,同时,为了解决最优路径的问题,我对PersonRequest
进行了封装,增加了中间层的属性,纵向电梯会在中间层对请求进行判断,若人到达了目标地点,则视为完成了该请求,否则会唤醒横向电梯线程对人进行换乘操作。
至于线程安全问题来说,由于我将共享的对象封装成了WaitingQueue
,并对WaitingQueue
取出和存入方法进行保护,同时,我的每一个电梯配有一个单独的WaitingQueue
属性,因此安全问题是比较好解决的。但是,在这次的作业中,最令我困扰的是如何结束程序的问题。考虑到输入结束后,仍可能存在某一个电梯需要调度起其他电梯的情况,因此就不能像前两次作业的一样,电梯在完成所有请求后就结束线程,需要电梯在完成自身的请求之后继续等待,直至所有电梯全部完成请求。为此,我的做法是在管理电梯的类Buildings
中新建了两个属性:threadInTotal
(总共电梯线程数)和threadEnd
(已经完成自身任务的电梯线程数),通过判断两者是否相等来判断程序是否已经完成了所有任务。代码如下:
if (super.getWaitingQueue().isEnd() && super.isPeopleInEmpty()
&& super.getWaitingQueue().isEmpty()) {
getBuildings().threadEndPM("PLUS");
synchronized (super.getWaitingQueue()) {
if (getBuildings().isEnd()) {
try {
super.getWaitingQueue().wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
if (!getBuildings().isEnd()) {
getBuildings().notifyAllBuilding();
return;
}
if (!(super.getWaitingQueue().isEmpty() && super.isPeopleInEmpty())) {
getBuildings().threadEndPM("MINUS");
continue;
}
getBuildings().threadEndPM("MINUS");
}
对于调度策略来说,我选择的是最短路径优先,同时当有多部电梯存在时,我会将请求放入任务最少的电梯中,但是,在后面的强测过程中,会出现一部电梯反复使用的情况,因此后面我改成了第二次作业中较为均衡的调度策略。
UML图
UML协作图
HACK策略
在HACK过程中,我主要考虑的是别人程序的在短时间内对于大量数据的处理能力以及对调度策略的处理,在实际过程中,这样的HACK策略对于一些别太明显的BUG还是有着一定的成功概率。
心得体会
总的来说,这单元的作业还是比较烧脑的,但是,由于我的每部电梯都有一个单独Queue类来进行维护,使得共享对象的数据有且仅有两个线程进行读和写,从而尽可能地避免了线程安全问题的发生。反而最令我烦恼的还是第三次作业中如何结束程序的问题。
在设计原则方面,这几次的作业,我主要遵循了SRP和OCP的设计原则。
对于BUG问题来说,我这几次的作业的BUG其实并不多,第一次作业的问题是线程输出安全的问题,第二次作业的问题是返回了NULL指针且没有进行判断保护,第三次作业的问题是最主要的还是电梯的调度。归根结底,我这几次作业出现BUG的原因还是由于没有进行足够的测试,自己测试的数据还是比较简单,只考虑到的程序的基本功能,并没有使用边界性的数据进行测试。这是我这两单元作业的一个痛点。