北航oo第二单元博客作业
北航oo第二单元总结
同步块的设置和锁的选择
在三次作业中,我都是使用了synchronized同步块,并且我对共享对象的方法均进行了同步,这种做法的好处比较简单,同时也能够避免线程安全的问题,保证线程安全。但这样自然也会使得程序的运行比较缓慢,有较大提升的空间。以下是共享对象中上锁的方法:
public synchronized void addRequest(Passenger passenger) {} public synchronized Passenger getOnePassenger() {} public synchronized void dequeue(Passenger request) {} public synchronized boolean isEmpty() {} public synchronized void setEnd(boolean end) {}
调度器的设计
第一次作业
关于调度器的设计,从第一次作业开始,我便采取了一个楼座拥有一个调度器的架构,这样也是为了方便后面的迭代开发,无论一个楼座中增加了几部电梯,均可以由该楼座的调度器进行分配。
public class VerticalController extends Thread { //passengerQueue是与电梯线程共享的对象,表示等待进入电梯的乘客队列 private final PassengerQueue passengerQueue; //requestQueue是与输入线程共享的对象,表示已输入的等待队列 private final RequestQueue requestQueue; //end标志线程是否结束 private boolean end = false; //tower表示该调度器所处的楼座,一个楼座拥有一个调度器 private final String tower; /*run方法是将requestQueue中的请求加入到passengerQueue中,随后电梯便自行从 passengerQueue中获取乘客*/ public void run() {} }
第二次作业
第二次作业由于新增了横向电梯,我便在第一次作业的基础上增加了横向的调度器类,同样也是采用一个楼层拥有一个调度器的架构,这样一个调度器就可以管理一个楼层所有的电梯。
public class HorizontalController extends Thread { //passengerQueue是与电梯线程共享的对象,表示等待进入电梯的乘客队列 private final PassengerQueue passengerQueue; //requestQueue是与输入线程共享的对象,表示已输入的等待队列 private final RequestQueue requestQueue; //end标志线程是否结束 private boolean end = false; //这是唯一与纵向电梯调度器不同的地方,表示该调度器所处的楼层 private final int floor; /*run方法是将requestQueue中的请求加入到passengerQueue中,随后电梯便自行从 passengerQueue中获取乘客*/ public void run() {} }
第三次作业
第三次作业增加了横向电梯有可达楼层的要求,我依旧是沿用了之前一个调度器管理一个楼座或楼层的架构,因此为了实现新的要求,在横向调度器中增加了一个ArrayList来记录该层电梯所有可达的楼座。
public class HorizontalController extends Controller { //记录了该层每部电梯可达的楼座,方便设置换乘路线 private ArrayList<ArrayList<String>> reachableTower = new ArrayList<>(); }
线程协同的架构模式
第一次作业
UML类图
分析和总结
第一次作业总是最伤脑筋的一次作业,一开始对于多线程的设计还是一头雾水,不知道要如何设计线程以及如何使线程之前进行交互,最后还是受到了实验课的启发,采用了与第一次实验课作业类似的架构。线程分为了输入线程、调度器线程、电梯线程三类。其中输入线程和调度器线程通过共享requestQueue队列进行交互,调度器线程与电梯线程通过共享passengerQueue队列进行交互。乘客的请求先输入至输入线程,输入线程根据乘客出发的楼座分配给对应调度器线程,最后调度器线程再分配乘客给电梯线程。而需要我完全独立思考实现的部分其实是电梯策略的设计。最后通过上网查阅了电梯调度的相关策略和阅读往年学长的博客,我选择了look策略作为电梯运行的策略。
第二次作业
UML类图
分析和总结
第二次作业主要是新增了横向电梯和多部电梯的请求,我选择了新增一个HorizontalElevator类作为横向电梯类,其实现方法与纵向电梯是大同小异的,电梯策略上同样是采用了look策略。而当存在多部电梯时,由于在第一次作业中已经考虑过这个问题,因此进行迭代开发时也比较轻松,无论是横向还是纵向电梯都采用了自由竞争策略。
第三次作业
UML类图
分析和总结
第三次作业最大的难点在于对换乘请求的处理,对于这个问题我其实并没有什么特别好的思路,最后是采用了比较简单的一种实现方法来完成换乘的请求,但同样简单的方法也导致了性能上会有些不太理想。我使用的解决换乘请求的方法为:将一个换乘请求分为三个部分,即纵-横-纵三个部分,完成前一个部分的请求后再将后一个部分的请求加入至乘客队列中直到所有部分的请求均完成。这样最大的问题就在于在横向部分时只能上直达的电梯(同时能够到达请求的出发楼座和目的楼座的电梯),而无法实现横向的换乘,像A->B,B->C这样的路径。
UML协作图
bug分析
第一次作业
第一次作业的bug是因为一开始电梯策略并没有设计的很好,所以导致强测中有几个点RTLE了,当时我想采用的是look策略,但在设计上并没有设计的很好,因此导致了不仅性能分低而且还会产生RTLE的bug。修复策略便是重新设计了电梯的调度策略,让电梯运行的性能提高了很多。
第二次作业
第二次作业是完成的最好的一次作业,没有发现什么bug,并且性能分也很高,有很多个点都拿了满分的性能分,并且大部分点也是97、98分的性能分。
第三次作业
第三次作业由于引入了不同电梯具有不同速度,在考虑的时候考虑得并不是很周全,在一些特殊情况下电梯会一直运行停不下来,导致在强测中有几个点又产生了RTLE的bug。修复策略是增加了一些特殊情况的判断,让电梯能够在没有乘客或者接不到人的时候都停下来。
hack策略
对于这一单元的hack我并没有什么很好的思路,同时多线程的运行也具有随机性,因此主要采取的都是随机投放了大量的数据来进行随缘hack的方法。有一些可以确定的hack策略是:hack输出线程是否安全,输出的时间戳是否递增。当电梯遇到大量请求时,能否处理超载的情况。除此之外我就没有想到别的很好的hack策略了。
心得体会
多线程的设计和线程安全
本单元主要考察的还是多线程的设计和如何保证线程安全的问题,保证线程安全的方法可以是同步块或加锁。由于这也是我第一次设计多线程的程序,在本单元作业中,我还是使用了比较简单的同步块来保证线程安全。尽管使用了同步块,但在实际设计电梯时还是遇到了两个问题。第一个是轮询的问题,如果让线程一直在while循环中运行,会导致cpu空转,cpu资源的浪费。因此在电梯没有乘客时一定要用wait方法来使电梯线程等待乘客请求再继续运行,为了让wait的线程继续运行,在新增乘客请求和进程结束时都要用notifyall方法来唤醒线程。第二个问题数据结构不安全的问题,一开始我在for循环中修改ArrayList时就会报错,上网查阅了相关资料后将ArrayList的数据类型换成了CopyOnWriteArrayList就不会再报错了。
架构设计
这一单元的作业更让我感受到了架构设计的重要性,无论在完成什么需求时都要先考虑好整体架构的设计,设计好了架构无论是从代码逻辑的清晰程度还是后续迭代开发上来讲,都是非常有好处的。这次也正因为第一次作业我认真设计好了架构,因此在后续两次作业的迭代开发中,都能够比较顺利地完成任务,不需要对程序进行太多的改动,没有进行重构。总的来说,这一单元的多线程设计体验是非常美妙的,也让我收获了很多多线程方面的知识,提升了架构设计的能力。