OO第二单元总结_多线程电梯

前言

与第一单元类似,本单元的三次作业也是逐次递进的,从多线程单部FAFS电梯到多线程单部可捎带电梯到多线程三部分楼层电梯。由于前两次作业采用了相似的架构,因此合并说明,第三次作业单独说明。

设计策略

三次作业采用了相似的设计模式,在此一并说明。在我的设计中,除去主线程之外有三种线程:输入处理、调度器和电梯。这三种线程之间涉及请求的生产与消费,很容易想到使用生产者-消费者模式,将请求队列作为共享对象,加锁确保线程安全。

avatar

输入线程每次得到新的输入请求,就将其放入与调度器共享的请求队列中,并唤醒调度器拉取新请求。调度器获得请求后,将新请求放入与电梯共享的请求队列,并唤醒电梯执行请求。当调度器或电梯发现队列中没有请求(且执行完自身获取的请求)时,进入wait状态,等待唤醒。

特别地,在第三次作业中,因为有多部电梯,调度器与三部电梯分别共享请求队列,调度器将请求放入选定的电梯对应的请求队列。

另一个非常关键的问题就是程序的退出。为了标记各个线程是否结束,需要一些布尔类型的变量。我选择将它们合并在一个GlobalFlag类中,采用单例模式,供各个线程读写。当上级线程退出Flag置位之后,唤醒下级线程,下级线程任务队列为空情况下,Flag置位并继续唤醒下一级线程。退出的逻辑参考下面的时序图。

avatar

为了保证线程安全,使用Synchronized对共享的任务队列加锁,保证读写操作的安全性。
此外,第三次电梯还涉及到请求拆分以及任务分配的问题,对于不能直达的请求,将其拆成先导请求和后续请求,先导请求和后续请求的投放由各级调度器控制,电梯不可见。二级调度器负责与电梯交互,将当前拿到的请求(一定是可执行的,不区分先导和和后续)平均分配给三部电梯(考虑等待人数和A、B、C三部电梯分配优先级递减的原则)。电梯负责根据捎带原则执行相应请求队列里的所有请求。

度量程序结构

第一次作业(FAFS电梯)

  1. 代码规模

    source file total lines source code lines source code lines% comment line counts comment line counts% black lines blank lines count%
    Elevator.java 63 51 81% 4 6% 8 13%
    HandleInput.java 35 27 77% 4 11% 4 11%
    Main.java 16 11 69% 2 13% 3 19%
    Scheduler.java 71 53 75% 9 13% 9 13%
    Total: 185 142 77% 19 10% 24 13%
  2. 类图

    avatar

    扫描二维码关注公众号,回复: 5982456 查看本文章

    这里为了方便将调度器和请求队列合并在一个类里面,没有作为线程出现。

  3. 方法&类分析

    Method ev(G) iv(G) v(G)
    oo.elevator1.Elevator.Elevator(int) 1 1 1
    oo.elevator1.Elevator.inout(int,boolean) 1 3 3
    oo.elevator1.Elevator.run() 1 3 3
    oo.elevator1.Elevator.updown(int,int) 1 2 2
    oo.elevator1.HandleInput.HandleInput() 1 1 1
    oo.elevator1.HandleInput.run() 3 4 4
    oo.elevator1.Main.main(String[]) 1 1 1
    oo.elevator1.Scheduler.Scheduler() 1 1 1
    oo.elevator1.Scheduler.getInstance() 1 1 3
    oo.elevator1.Scheduler.get_mark() 1 1 1
    oo.elevator1.Scheduler.get_request() 1 5 5
    oo.elevator1.Scheduler.put_request(PersonRequest) 1 2 2
    oo.elevator1.Scheduler.set_end() 1 1 1
    Class OCavg WMC
    oo.elevator1.Elevator 1.75 7
    oo.elevator1.HandleInput 2 4
    oo.elevator1.Main 1 1
    oo.elevator1.Scheduler 1.67 10

    第一次作业采用了傻瓜式的FAFS调度,因此各个部分的复杂度相对较低。整个程序只有一个共享对象,耦合相对较低。

第二次作业(单部可稍带电梯)

  1. 代码规模

    source file total lines source code lines source code lines% comment line counts comment line counts% black lines blank lines count%
    Dispatcher.java 58 42 72% 10 17% 6 10%
    Elevator.java 207 162 78% 30 14% 15 7%
    GlobalFlag.java 45 32 71% 6 13% 7 16%
    HandleInput.java 38 28 74% 6 16% 4 11%
    Main.java 26 17 65% 5 19% 4 15%
    Panel.java 117 84 72% 16 14% 17 15%
    Requestqueue.java 180 132 73% 31 17% 17 9%
    Total: 671 497 74% 104 0.154993 70 10%
  2. 类图

    avatar

    这一次将请求队列与调度器分离,调度器作为单独的线程,请求队列作为共享对象。Panel是电梯的状态板,在此次作业中采用了“楼层亮灯”的机制控制电梯开关门,状态板也作为共享对象出现(在第三次被修改了),在调度器部分实现了可稍带算法,而电梯只负责在亮灯的楼层开关门。

  3. 方法&类分析

    Method ev(G) iv(G) v(G)
    alslift.Dispatcher.Dispatcher(Requestqueue,Panel,Requestqueue) 1 1 1
    alslift.Dispatcher.run() 1 3 3
    alslift.Dispatcher.transport_request() 1 2 2
    alslift.Elevator.Elevator(Panel,Requestqueue) 1 1 1
    alslift.Elevator.check_inout() 3 1 3
    alslift.Elevator.check_inside() 4 2 4
    alslift.Elevator.check_liftqueue() 1 1 1
    alslift.Elevator.inout() 1 4 5
    alslift.Elevator.inside_out(int) 1 4 4
    alslift.Elevator.norm_move() 3 5 6
    alslift.Elevator.outside_in(int,ArrayList ) 1 1 1
    alslift.Elevator.printinside() 1 3 3
    alslift.Elevator.run() 1 3 3
    alslift.Elevator.updown() 2 2 8
    alslift.GlobalFlag.GlobalFlag() 1 1 1
    alslift.GlobalFlag.getInputend() 1 1 1
    alslift.GlobalFlag.getInstance() 1 1 3
    alslift.GlobalFlag.getSchedulerend() 1 1 1
    alslift.GlobalFlag.setInnputend() 1 1 1
    alslift.GlobalFlag.setSchedulerend() 1 1 1
    alslift.HandleInput.HandleInput(Requestqueue) 1 1 1
    alslift.HandleInput.run() 3 4 4
    alslift.Main.main(String[]) 1 1 1
    alslift.Panel.Panel(int,int,int) 1 1 1
    alslift.Panel.getCurrentfloor() 1 1 1
    alslift.Panel.getInfo() 1 1 1
    alslift.Panel.getMajor() 1 1 1
    alslift.Panel.getState() 1 1 1
    alslift.Panel.init_state() 1 2 4
    alslift.Panel.light_init() 1 3 3
    alslift.Panel.light_on(int) 1 1 1
    alslift.Panel.setCurrentfloor(int) 1 1 1
    alslift.Panel.setMajor(PersonRequest) 1 1 1
    alslift.Panel.setState(int) 1 1 1
    alslift.Panel.turn_off(int) 1 1 1
    alslift.Panel.turn_on(int) 1 1 1
    alslift.Panel.update_state() 1 2 3
    alslift.Requestqueue.Requestqueue() 1 1 1
    alslift.Requestqueue.can_pick(PersonRequest,int,int) 3 4 5
    alslift.Requestqueue.empty_wait(int) 3 7 7
    alslift.Requestqueue.get_first() 1 1 1
    alslift.Requestqueue.judge(PersonRequest,PersonRequest,int,int,int) 3 2 3
    alslift.Requestqueue.merge_request(ArrayList ) 1 2 2
    alslift.Requestqueue.notify_elevator() 1 1 1
    alslift.Requestqueue.outside_in(int,ArrayList ,Panel) 1 4 4
    alslift.Requestqueue.put_request(PersonRequest) 1 2 2
    alslift.Requestqueue.request_clear() 1 1 1
    alslift.Requestqueue.same_side(PersonRequest,PersonRequest,int) 3 4 5
    alslift.Requestqueue.traverse_check(int) 4 2 4
    alslift.Requestqueue.traverse_input(int,int,int,PersonRequest) 1 5 5
    Class OCavg WMC
    alslift.Dispatcher 2 6
    alslift.Elevator 2.91 32
    alslift.GlobalFlag 1.33 8
    alslift.HandleInput 2 4
    alslift.Main 1 1
    alslift.Panel 1.5 21
    alslift.Requestqueue 2.46 32

    此次在Elevator类和Requestqueue类里,一些涉及遍历的方法导致复杂度高,但采用了亮灯策略之后这些方法没有被调用。(提交的时候忘记删了)将Panel作为请求队列之外的共享对象的做法由诸多问题,虽然避免了对请求队列的多次遍历,但实际上使锁的控制逻辑更加复杂,容易引入更多的bug。

第三次作业(三部电梯,分楼层)

  1. 代码规模

    source file total lines source code lines source code lines% comment line counts comment line counts% black lines blank lines count%
    AbcFloor.java 60 50 83% 1 2% 9 15%
    Backup.java 28 18 64% 3 11% 7 25%
    Elevator.java 216 190 88% 13 6% 13 6%
    Follow.java 46 33 72% 7 15% 6 13%
    Former.java 72 51 71% 12 17% 9 13%
    GlobalFlag.java 91 68 75% 10 11% 13 14%
    HandleInput.java 38 28 74% 6 16% 4 11%
    LowDispatcher.java 106 84 79% 14 13% 8 8%
    Main.java 52 40 77% 1 2% 11 21%
    Panel.java 98 71 72% 11 11% 16 16%
    RequestQueue.java 130 102 78% 13 10% 15 12%
    TopDispatcher.java 97 76 78% 13 13% 8 8%
    Total: 1034 811 78% 104 10% 119 12%
  2. 类图

    avatar
    点击查看原图

    这一次的架构上出现了一些冗余,比如三级调度器是不必要的,两级就可以实现相同的功能,将后续请求调度器单独拆分成一个线程虽然减少了二级调度器和电梯之间共享对象的个数,但导致线程之间的唤醒和等待逻辑更为复杂。这里的Panel与第二次的电梯不同,不再是共享对象,仅仅是为了将电梯状态相关的属性和方法与电梯线程本身分离开降低复杂度。

  3. 方法&类分析

    Method ev(G) iv(G) v(G)
    sslift.AbcFloor.AbcFloor() 1 1 1
    sslift.AbcFloor.acan(int,int) 1 2 2
    sslift.AbcFloor.ainit() 1 3 3
    sslift.AbcFloor.bcan(int,int) 1 2 2
    sslift.AbcFloor.binit() 1 3 3
    sslift.AbcFloor.ccan(int,int) 1 2 2
    sslift.AbcFloor.cinit() 1 2 2
    sslift.Backup.Backup() 1 1 1
    sslift.Backup.get_request(String) 1 1 1
    sslift.Backup.put_request(PersonRequest) 1 1 1
    sslift.Elevator.Elevator(RequestQueue,Panel,Former,int) 1 1 1
    sslift.Elevator.check_floor(int) 1 2 3
    sslift.Elevator.check_in(int,boolean) 2 3 3
    sslift.Elevator.check_out(int) 3 2 3
    sslift.Elevator.in(int) 1 1 2
    sslift.Elevator.inout(boolean,boolean,int) 1 9 9
    sslift.Elevator.norm_move() 3 6 7
    sslift.Elevator.out(int) 1 5 5
    sslift.Elevator.run() 4 5 5
    sslift.Elevator.updown(int,int) 1 4 10
    sslift.Follow.Follow(Former,Backup,RequestQueue) 1 1 1
    sslift.Follow.run() 3 4 4
    sslift.Follow.transfer_backup(ArrayList ) 1 2 2
    sslift.Former.Former() 1 1 1
    sslift.Former.add_term(int) 1 1 1
    sslift.Former.change_term(int) 1 2 2
    sslift.Former.check_ready() 1 3 3
    sslift.Former.empty() 1 1 1
    sslift.Former.follow_wait() 3 4 5
    sslift.Former.request_clear() 1 1 1
    sslift.GlobalFlag.GlobalFlag() 1 1 1
    sslift.GlobalFlag.getFollowend() 1 1 1
    sslift.GlobalFlag.getInputend() 1 1 1
    sslift.GlobalFlag.getInstance() 1 1 3
    sslift.GlobalFlag.getLowend() 1 1 1
    sslift.GlobalFlag.getTopend() 1 1 1
    sslift.GlobalFlag.judge_exit() 1 2 4
    sslift.GlobalFlag.setEend(int) 1 1 3
    sslift.GlobalFlag.setFollowend() 1 1 1
    sslift.GlobalFlag.setInputend() 1 1 1
    sslift.GlobalFlag.setLowend() 1 1 1
    sslift.GlobalFlag.setTopend() 1 1 1
    sslift.HandleInput.HandleInput(RequestQueue) 1 1 1
    sslift.HandleInput.run() 3 4 4
    sslift.LowDispatcher.LowDispatcher(RequestQueue,RequestQueue,RequestQueue,RequestQueue,AbcFloor) 1 1 1
    sslift.LowDispatcher.dispatch_request(PersonRequest) 1 7 7
    sslift.LowDispatcher.elevator_can(int,int) 1 1 4
    sslift.LowDispatcher.run() 3 4 4
    sslift.LowDispatcher.three_choice(PersonRequest) 1 3 5
    sslift.LowDispatcher.two_choice(PersonRequest,RequestQueue,RequestQueue) 1 2 2
    sslift.Main.elevator_start(RequestQueue,RequestQueue,RequestQueue,Panel,Panel,Panel,Former) 1 1 1
    sslift.Main.main(String[]) 1 1 1
    sslift.Panel.Panel(int) 1 1 1
    sslift.Panel.changeSpace(int) 1 1 1
    sslift.Panel.getCurrentfloor() 1 1 1
    sslift.Panel.getMajor() 1 1 1
    sslift.Panel.getSpace() 1 1 1
    sslift.Panel.getState() 1 1 1
    sslift.Panel.init_state() 1 1 4
    sslift.Panel.release() 1 1 1
    sslift.Panel.saveone() 1 1 1
    sslift.Panel.setCurrentfloor(int) 1 1 1
    sslift.Panel.setMajor(PersonRequest) 1 1 1
    sslift.Panel.setSpace(int) 1 1 1
    sslift.Panel.update_state() 1 1 3
    sslift.RequestQueue.RequestQueue() 1 1 1
    sslift.RequestQueue.can_pick(PersonRequest,PersonRequest,int) 1 1 1
    sslift.RequestQueue.check_in(int,PersonRequest) 4 3 4
    sslift.RequestQueue.empty() 1 1 1
    sslift.RequestQueue.empty_wait(int) 5 8 9
    sslift.RequestQueue.get_first() 1 1 1
    sslift.RequestQueue.get_in(int,int,PersonRequest,int) 1 5 5
    sslift.RequestQueue.get_request() 1 1 1
    sslift.RequestQueue.printin(int,PersonRequest,int) 1 3 3
    sslift.RequestQueue.put_request(PersonRequest) 1 2 2
    sslift.RequestQueue.request_clear() 1 1 1
    sslift.RequestQueue.size() 1 1 1
    sslift.TopDispatcher.TopDispatcher(RequestQueue,RequestQueue,Former,Backup,AbcFloor) 1 1 1
    sslift.TopDispatcher.divide_request(PersonRequest) 1 2 2
    sslift.TopDispatcher.handle_two(PersonRequest,PersonRequest,int) 1 1 1
    sslift.TopDispatcher.judge_single(int,int) 1 3 3
    sslift.TopDispatcher.run() 3 4 4
    sslift.TopDispatcher.spilt_two(int,int,int) 1 1 12
Class OCavg WMC
sslift.AbcFloor 1.71 12
sslift.Backup 1 3
sslift.Elevator 3.8 38
sslift.Follow 2 6
sslift.Former 1.71 12
sslift.GlobalFlag 1.42 17
sslift.HandleInput 2 4
sslift.LowDispatcher 3.33 20
sslift.Main 1 2
sslift.Panel 1.38 18
sslift.RequestQueue 2 24
sslift.TopDispatcher 2.5 15

此次将稍带策略放在了电梯里完成,导致复杂度较高(包括遍历请求队列、遍历并维护电梯内部请求队列等等)。在底层调度器LowDispatcher中,有对电梯是否能携带请求的判断和分配给电梯的算法,复杂度较高。在设计原则方面,这一次出现了一些问题,包括冗余的共享对象former,用于标记先导请求是否已经完成,完全可以通过修改请求队列的方式进行,不必单独拿出来,还增加了一次根据id检索请求的开销。

bug分析

  • 发现了以下几种bug,对应解决方案
    • 程序不退出:第一次写多线程电梯,将“强制在线”理解成了程序不需要退出,导致第一次提交每个测试点花费了210s,之后设计了退出逻辑解决了这一问题
    • 死等问题:在使用wait notify机制时,需要注意唤醒的时机,最初有一些程序不能正常退出的情况是因为调度器notifyAll的时候电梯还在运行,之后电梯wait的时候不再被唤醒,从而程序不能正常退出。需要在电梯判空等待的时候判断是否已经到了结束的时机,则不必wait直接退出。
    • CPU_TIME_LIMIT_EXCEED:我遇到这个bug是因为在上级线程退出flag置位之后,由于置位操作可能早于下一级读取操作notifyAll出现的时候下一级线程还没有运行结束,之后判空退出的位置出现了轮询(相当于wait notify机制没有正常起作用)

一点期望

希望之后能够尝试泛型和接口的使用,减少共享对象来降低耦合。另外阻塞队列确实比自己加锁出现bug的概率低一些。关于更简便而且有效的电梯调度策略,可能有机会再接触吧,比如图算法实现的多部电梯分层调度似乎还是比look或者scan高效一些。

猜你喜欢

转载自www.cnblogs.com/frozen-blog/p/10754906.html