OO-第二单元总结

一、三次作业的设计策略

  (1). 第五次作业

    第五次作业由于较为简单,在强测及互测中均没有出现BUG,但是并没有做优化。本次的设计有些不合理,所以在后面的作业中也做了重构。本次的作业主要有三个类,主函数类,指令类,和电梯类。其中主函数中完成输入的操作,并且每获得一条指令就开启一个指令线程(严重不合理之处,自己当时理解错误),指令类中的run方法是每次开始就wait,直到电梯送完一个人才会notifyall,唤醒所有指令线程,如果有一个指令能够进入电梯,则运行电梯,其他指令线程继续wait。这种设计在后来进行了修改。在后来我也发现了这种开多个线程的写法性能十分低下。

  (2). 第六次作业

    第六次作业我认为较为困难。开始时的设计是一共有电梯,调度器和电梯三个线程,但是在调度器和电梯之间是通过电梯每到达一层就会notify调度器,由调度器将等待队列中的,能够上电梯的乘客放入电梯的乘客队列,然后notify电梯,让电梯启动。但是这种设计实际上存在问题,由于自己当时对锁的理解并不深入,后来才发现这种相互notify的设计线程不安全,会有电梯还没来的及进入wait,调度器就已经notify电梯了,造成电梯真正进入wait状态时无法被唤醒,造成错误。(这也是我无效作业的原因)。后来将调度器改为每次遍历完当前等待队列,将人塞入电梯后就wait,直到电梯到达某一层,并且状态是wait的时候才会进入下一次遍历。这种设计通过了强测,也让我对线程的锁理解更加的深入。

  (3). 第七次作业

    第七次作业我一开始的想法有些复杂。开始时试图沿用上一次的策略,电梯每到一层,由调度器将能够上电梯的人塞入电梯,但是这次由于有三种电梯,且如果这样写的话换乘电梯较为复杂,所以最后并没有成功实现。后来的想法是,有三个电梯线程,一个调度器线程,一个输入线程。电梯负责将本电梯的等待队列捎带进入电梯,调度器负责将能直达的指令塞给电梯的等待队列,将无法直达的指令通过计算出两层间能够从哪层换乘其他电梯来拆分指令,并分为两部分塞入电梯和存入乘客类的副指令中,在人出电梯时才将副指令加入指令队列中。这种调度虽然无法让其他电梯在能够接人时提前过去,但是在最大限度上保证了线程安全,最后在强测及互测中正确性表现良好。

二、基于度量分析自己的程序结构

  (1). 第五次作业

   类图:

         

   时序图:

     

        算法主要是:

          •   主函数负责输入,并将输入的request new 一个Intruction对象并线程开启。
          •    Instruction类中传入了Elevator对象,通过调用Elevator的方法实现控制电梯;run方法主要先判断如果电梯在被使用则继续wait。否则抢电梯,运行电梯,最后notifyall,唤醒     在等待中的指令线程。
          •    Elevator类中主要有电梯的上下行,去用户所在楼层,开关门的方法。

    复杂度分析:

      

      

    总体可见,复杂度还是可以的。但是算法存在明显缺陷。这种算法虽然在第二次作业中并没有复用,但是可想而知开很多线程开销会很大。实际上可以写成第二次的电梯的算法。

    根据SOLID原则,类的职责较为单一;只有继承Thread类;虽然没有使用接口类但是本次作业似乎并没有必要实现接口;满足最少知道原则,各类只public必要的方法;满足开闭原则。 

  (2). 第六次作业

    类图:

      

    时序图:

      

      算法:

      • 主函数负责开启3个线程,Elevator,Instructor ,Input
      • Input线程主要负责输入,将输入的request改为Passenger对象,并存入Instructor调度器对象中。(在输入截至时会notify在等待的线程,以满足平稳结束程序)。
      • Instructor线程负责将传入的指令中能够进入电梯的指令(电梯为空;或from与当前楼层一致,且与电梯当前运行方向一致)放入电梯的乘客队列。
      • Elevator线程负责上下行,开关门的工作,但每到一层,会wait,等待调度器(Instructor)将能够捎带的指令加入电梯后再运行。

     复杂度分析:

        

        

     总体可见本次设计中复杂度控制的并不很好。Elevator的run方法和Instructor的run方法都较复杂。

        根据SOLID原则,类的职责较为单一;只有继承Thread类;没有使用接口类;满足最少知道原则,各类只public必要的方法;开闭原则没有很好的满足(在结束时为了满足通知电梯停止而修改了电梯内部的标志,除了这种方法暂时还没有想到较为合适的通讯方法)。

  (3). 第七次作业

    类图:

      

    时序图:

      

       算法:

        • 主函数类主要用于开启5个线程,Input线程,Controller线程,和3个Elevator线程。
        • Passenger类保存了指令的信息以及无法直达指令的被拆分出来的第二条指令。
        • Input线程用于将输入的request转为passenger类并存入Requests类中的总队列中。
        • Controller线程首先遍历总队列,将可以直达的指令放入相应电梯的等待队列中,然后将无法直达的指令拆为两部分(根据矩阵,打表得来的),前半部分(有from及表中的to)作为可直达指令存入相应电梯的等待队列中,后半部分(表中的to和真实的to)存在乘客的sub中。
        • Elevator线程负责将本电梯的等待队列中的人按照ALS算法送达目的地。在乘客到达目的地时,判断下此乘客的sub是否为空,为空则直接出电梯,否则将此乘客的sub指令存入Requests的总队列中。     

    复杂度分析:

      

        

     总体可见本次设计中复杂度控制的并不很好。Elevator的run方法和Instructor的run方法都较复杂。

        根据SOLID原则,本次类的职责没有完全单一(Elevator类在实现工作职责的同时还实现了捎带,实际应该使用第二级调度器,但是为了保证线程安全并没有实现);只有继承Thread类;没有使用接口类;满足最少知道原则,各类只public必要的方法;开闭原则没有很好的满足(在结束时为了满足通知电梯停止而修改了电梯内部及调度器内部的标志)。

三、分析自己的BUG

  (1). 第五次作业

    本次作业由于较为简单并未发现BUG

  (2). 第六次作业

    本次作业开始时的写法存在调度器与电梯相互notify的写法,这种方法我发现是线程不安全的,很容易出现一个线程还没有来得及进入wait状态就被notify了,而notify只会执行一次,就导致无法被正常唤醒(加标志位也会存在类似的问题);后来的方法自己在测试时发现是使用synchronized时加错了锁,锁的是elevator对象而不是整个run方法导致最后没有改对。

  (3). 第七次作业

    第七次作业并未被发现BUG,自己测试时的BUG主要是轮询造成的。

四、发现别人BUG采用的策略

  主要通过精心构造数据,并使用按键精灵输入;在各种时刻ctrl + D以判断能否正常退出;还有为了能够精准在0秒输入,使用先使主线程sleep5秒后再输入,这样能够避免输入的误差(学习自讨论区)。第二单元的测试与第一单元的差异主要是第二单元为多线程问题,有些线程安全问题复现一次难度较大(就如第六次作业开始时相互notify这种写法,运气好时结果时正确的)。而且有些样例需要定时输入。

五、心得体会

  第一次接触多线程,由于自己初期的理解错误造成了第六次作业翻车。但这也使我对synchronized的使用有了更深的理解,比如synchronized锁的是括号中的对象,也可以锁当前对象,notify的则是拥有当前锁的对象。在实验课中学习到,若要实现两个进程间相互notify,可以为两个进程传入相同的Object对象,然后再锁这个对象即可,解锁时同理。第六次作业翻车可能的原因还有自己的心态问题,改到周二晚些时候有些着急。第七次作业实际上也经历了重构,但是基本在两个小时内就完成了重构(还将原来只有上下行等功能的电梯重构为了能够捎带的电梯。。。),希望以后能够更好的调整自己的心态,完全理解好之后再动手写代码。

猜你喜欢

转载自www.cnblogs.com/saluki/p/10756774.html