OO第二单元总结

17231041  金陆洋

        这一单元开启了多线程的旅途,从最开始的FAFS,到ASL,再到最后的SS,每一次的难度都有一个很大的提升,尤其是最后一次作业的时候,经常会让我领略到凌晨四点的北航(当然,也有OS的功劳),当然,在这个痛苦的过程中,我的能力也得到了一次又一次的提升(应该吧~~~),当然和大佬相比还是有很大的差距。

        接下来进入正题,先来讲一讲这三次作业中对多线程的一些个人拙见,可能有些片面,各位看官笑笑即可,如若能在评论区指点几句就更加感激不尽了。

与暴力轮询say goodbye——从让它睡觉到让它等着

        面对共享对象(在这次作业中,即RequestList)的访问,一个永远逃不开的问题就是如何避免暴力轮询,一种方式是sleep(time)让线程休眠一段时间,另一种方法是wait/notify使线程暂时阻塞直到被唤醒。

  由于第一次作业中我不太敢用wait/notify,对这两个方法的功能也不甚了解,因此采用了简单粗暴的sleep()方法,让电梯分配器为空的时候每访问一次RequestList便休眠一段时间才能进行下一次的访问,其实这样做似乎没什么问题,更准确地说,正确性并没有受到影响,如此简易高效的做法我本可能沿用到之后的两次作业中的,直到某位大佬对我说了这样一句话:

感觉你这样做不够美观。

  使用sleep固然不会错,可这样做好吗,答案显然应该是否定的。

  或许一个电梯的时候sleep还可以,但多电梯呢?多调度策略呢?很显然,sleep不能很好地支持代码的健壮性和可复用性,为了面对将来纷繁复杂的情况,我们显然应该使用wait/notify来解决轮询。

这就好比人家给了我点钞机,我却要一张一张自己点,一张两张效率可能更高,但一万张呢?

  因此,从第二次作业开始,我换用了wait/notify,其实真的蛮好用的,尤其是第三次作业的时候,而且似乎wait/notify的性能比单纯sleep还要好一点?

  其实sleep有个值得注意的问题,如果我哪天脑抽把sleep写在了synchronized方法中,那可能真的gg了。

  在使用wait/notify的时候,我也越加深刻地体会到了代码构架的重要性,只有将构架完整地搭建好了,使用wait/notify才会真正万无一失。

  因此,真正好的构架应该是一以贯之行云流水的的,而绝不是缝缝补补拆东墙补西墙的,这在我的第二次电梯作业中体现得最为明显,在这次作业中,我第一次使用了wait/notify,自然少不了四处碰壁,也发现了构架的不少小问题,我选择了直接重构,其实也并未花费多长时间,反而可能比一遍又一遍地找bug更快,而且从根本上解决了众多bug(虽然性能分不高)。但其实如果我能在写代码之前再认真思考一下架构问题,而不那么急于动手,应该也就不需要这一次的重构了,也算是一个教训了。

  于是,第三次电梯作业我用了一个晚上从头到尾地做好了构架,真正写代码的时候反而很轻松。

  可以说,时至今日,我才真正体会到了一个好的构架的重要性。

  不要重复造轮子,指导书中如是说,我原来不明白这句话的真正含义,现在才发觉,原来我之前都在重复造轮子。

多线程冲突——synchronized大法真的好吗

  作为Java尤其是多线程的初学者,只要涉及到共享对象的访问就加一个synchronized,这样似乎万事大吉了?

  如果没有一个良好的构架作为基础,那么势必会导致synchronized关键字出现频率异常多,而互斥锁用得越多,也就更有可能出现难以预料的线程问题,这是在所难免的。

  因此,synchronized应该是用得越少越好(当然,在保证线程安全的前提下),这也就需要一个好的架构来实现解耦,这样,似乎又回到了上一个问题。

  解决线程冲突问题,也可以使用不可变对象,这也是行之有效的方法,但由于电梯作业的增删改查过于频繁,使用不可变对象的代价太大,因此最终放弃了这个想法(应该是我太菜了没有想到使用的方法)。

线程的结束——一起停还是分别停

  这个问题,其实就不是那么重要了,两种策略在我看来无孰优孰劣,根据自己的构架选择更加适合的即可。

  我使用的是分别停止的方法,在每个电梯完成了所有的工作后(并且不会再有新的请求)即可使线程结束,所有线程结束,程序正常退出。

FAFS电梯总结

  

  这一次的作业较为简单,Schedule作为共享对象,InputHandler进行存储,Elevator进行拿取(由于比较简单,我没有使用Dispatcher,可拓展性并不够好)。

  

method ev(G) iv(G) v(G)
oo.Elevator.changeClose() 1.0 1.0 1.0
oo.Elevator.pickUp() 1.0 6.0 6.0
oo.Elevator.run() 5.0 7.0 8.0
oo.Elevator.send() 1.0 6.0 6.0
oo.InputHandle.run() 3.0 4.0 4.0
oo.Main.main(String[]) 1.0 1.0 1.0
oo.Schedule.getOut() 1.0 1.0 1.0
oo.Schedule.getState() 1.0 1.0 1.0
oo.Schedule.goInto(int,int,int) 1.0 1.0 1.0
Total 15.0 28.0 29.0
Average 1.67 3.11 3.22

class OCavg WMC
oo.Elevator 3.5 14.0
oo.InputHandle 3.0 3.0
oo.Main 1.0 1.0
oo.Schedule 1.0 3.0
Total   21.0
Average 2.33 5.25

 

 

   只有Elevator的run方法复杂度较高,这是因为我将分配器(Dispatcher)以及电梯(Elevator)合二为一,没有实现真正的解耦。

bug:

  本次作业较为简单,并未发现自己和他人的bug。

ASL电梯总结

  由于水平较为有限,也比较怕互测被极端数据卡时间,因此采用了官方推荐ASL算法。

  这次电梯作业,我采取了Dispatcher和Elevator分离的构架。

   RequestList作为共享对象(即容器),InpurHandler发送新的请求信息,Dispatcher负责获取所需信息并调度电梯(Elevator)。

    注:分配器是线程而电梯不是线程,避免两个线程间的安全问题。

method ev(G) iv(G) v(G)
oo.Dispatcher.carryGo() 4.0 7.0 9.0
oo.Dispatcher.Dispatcher(RequestList) 1.0 1.0 1.0
oo.Dispatcher.emptyGo() 1.0 5.0 7.0
oo.Dispatcher.goOut(int) 1.0 3.0 3.0
oo.Dispatcher.goOutEm(int) 1.0 4.0 4.0
oo.Dispatcher.into(ArrayList<arraylist>) 1.0 2.0 2.0
oo.Dispatcher.inWait() 1.0 2.0 2.0
oo.Dispatcher.run() 4.0 5.0 5.0
oo.Elevator.close(int) 1.0 2.0 2.0
oo.Elevator.getFlag() 1.0 1.0 1.0
oo.Elevator.move(int) 1.0 2.0 2.0
oo.Elevator.open(int) 1.0 3.0 3.0
oo.InputHandler.InputHandler(RequestList) 1.0 1.0 1.0
oo.InputHandler.run() 3.0 4.0 4.0
oo.Main.main(String[]) 1.0 1.0 1.0
oo.RequestList.getNum() 1.0 1.0 1.0
oo.RequestList.getOut() 1.0 3.0 3.0
oo.RequestList.goInto(int,int,int) 1.0 1.0 1.0
oo.RequestList.lookUp(int,int,int) 1.0 13.0 13.0
Total 27.0 61.0 65.0
Average 1.42 3.21 3.42

class OCavg WMC
oo.Dispatcher 4.0 32.0
oo.Elevator 1.5 6.0
oo.InputHandler 2.0 4.0
oo.Main 1.0 1.0
oo.RequestList 2.75 11.0
Total   54.0
Average 2.84 10.8

  可以看出,对于共享对象的遍历查询(即捎带过程)的复杂度较高(表格中的lookUp方法),其实这在我的意料之中,因为判断每一请求能否捎带的确需要多重判断条件,现在想一想,把这些判断条件单独封装起来应该会更好,这是一个不足的地方。

另附上代码:

    public synchronized ArrayList<ArrayList<Integer>> lookUp(
            int now, int waiting, int to) {
        ArrayList<ArrayList<Integer>> result = new ArrayList<>();
        for (int i = 0; i < requestList.size(); i++) {
            ArrayList<Integer> tem = requestList.get(i);
            if (tem.get(1) == now) {
                if (now < to && now < tem.get(2)) {
                    result.add(requestList.remove(i));
                    i--;
                } else if (now > to && now > tem.get(2)) {
                    result.add(requestList.remove(i));
                    i--;
                } else if (waiting != 0 &&
                        now > tem.get(2) && tem.get(2) > waiting) {
                    result.add(requestList.remove(i));
                    i--;
                } else if (waiting != 0 &&
                        now < tem.get(2) && tem.get(2) < waiting) {
                    result.add(requestList.remove(i));
                    i--;
                }
            }
        }
        return result;
    }

 

bug:

  由于我并未做实质上的优化,故强测成绩并不高,大家可能都没有测评机,因此并未发现bug。

SS电梯总结

  这次作业我复用了上次作业的ASL调度算法作为每一个电梯的调度策略并扩展成了三部电梯(使用了三个分配器)。

  看起来有点复杂,其实与上次作业的区别仅在于使用了三个分配器,另外我新建了SyOutput类为输出方法加上了锁以避免不安全的问题(但似乎没必要?)。

method ev(G) iv(G) v(G)
oo.DispatcherFirst.carryGo() 7.0 10.0 12.0
oo.DispatcherFirst.DispatcherFirst(RequestList) 1.0 1.0 1.0
oo.DispatcherFirst.emptyGo() 1.0 5.0 7.0
oo.DispatcherFirst.goOut(int) 1.0 4.0 4.0
oo.DispatcherFirst.goOutEm(int) 1.0 5.0 5.0
oo.DispatcherFirst.into(ArrayList<arraylist>) 1.0 2.0 2.0
oo.DispatcherFirst.inWait() 1.0 2.0 2.0
oo.DispatcherFirst.run() 4.0 5.0 5.0
oo.DispatcherSecond.carryGo() 7.0 10.0 12.0
oo.DispatcherSecond.DispatcherSecond(RequestList) 1.0 1.0 1.0
oo.DispatcherSecond.emptyGo() 1.0 5.0 7.0
oo.DispatcherSecond.goOut(int) 1.0 4.0 4.0
oo.DispatcherSecond.goOutEm(int) 1.0 5.0 5.0
oo.DispatcherSecond.into(ArrayList<arraylist>) 1.0 2.0 2.0
oo.DispatcherSecond.inWait() 1.0 2.0 2.0
oo.DispatcherSecond.run() 4.0 5.0 5.0
oo.DispatcherThird.carryGo() 7.0 10.0 12.0
oo.DispatcherThird.DispatcherThird(RequestList) 1.0 1.0 1.0
oo.DispatcherThird.emptyGo() 1.0 5.0 7.0
oo.DispatcherThird.goOut(int) 1.0 4.0 4.0
oo.DispatcherThird.goOutEm(int) 1.0 5.0 5.0
oo.DispatcherThird.into(ArrayList<arraylist>) 1.0 2.0 2.0
oo.DispatcherThird.inWait() 1.0 2.0 2.0
oo.DispatcherThird.run() 4.0 5.0 5.0
oo.Elevator.close(int) 1.0 2.0 2.0
oo.Elevator.Elevator(int,char) 1.0 1.0 1.0
oo.Elevator.getFlag() 1.0 1.0 1.0
oo.Elevator.move(int) 1.0 2.0 2.0
oo.Elevator.open(int) 1.0 3.0 3.0
oo.InputHandler.InputHandler(RequestList) 1.0 1.0 1.0
oo.InputHandler.run() 3.0 5.0 5.0
oo.Main.main(String[]) 1.0 1.0 1.0
oo.RequestList.check(int,int,int) 2.0 5.0 5.0
oo.RequestList.clear(ArrayList<arraylist>) 1.0 3.0 3.0
oo.RequestList.firstAvailable(int,int) 1.0 1.0 4.0
oo.RequestList.getOutFirst() 4.0 5.0 7.0
oo.RequestList.getOutSecond() 4.0 5.0 7.0
oo.RequestList.getOutThird() 4.0 5.0 7.0
oo.RequestList.lookUpFirst(int,int,int,int) 9.0 15.0 18.0
oo.RequestList.lookUpSecond(int,int,int,int) 9.0 15.0 18.0
oo.RequestList.lookUpThird(int,int,int,int) 9.0 15.0 18.0
oo.RequestList.max(int,int) 2.0 1.0 2.0
oo.RequestList.min(int,int) 2.0 1.0 2.0
oo.RequestList.ready(ArrayList<arraylist>) 4.0 3.0 5.0
oo.RequestList.reset(int,int) 1.0 10.0 10.0
oo.RequestList.returnAl(int,int,int,int,int) 1.0 1.0 1.0
oo.RequestList.secondAvailable(int,int) 1.0 1.0 8.0
oo.RequestList.separate(int,int,int) 3.0 17.0 18.0
oo.RequestList.set(int) 1.0 7.0 7.0
oo.RequestList.specialHandle(int,int,int) 1.0 5.0 9.0
oo.RequestList.thirdAvailable(int,int) 1.0 1.0 6.0
oo.SyOutput.initStartTimestamp() 1.0 1.0 1.0
oo.SyOutput.println(String) 1.0 1.0 1.0
Total 123.0 236.0 287.0

class OCavg WMC
oo.DispatcherFirst 4.625 37.0
oo.DispatcherSecond 4.625 37.0
oo.DispatcherThird 4.625 37.0
oo.Elevator 1.4 7.0
oo.InputHandler 2.5 5.0
oo.Main 1.0 1.0
oo.RequestList 4.79 91.0


  可以看出来,绝大多数的方法复杂度均较低,问题还是出现在共享对象的遍历上,这一点是我下一单元改进的重中之重。

bug:

  这一次,我和其他同学共同搭建了测评机,用来自测和互测(也想当一回【假】狼人)。

  但我这个废物测评机竟然在我手里没有发现任何bug(其他使用这架测评机的同学都或多或少地找到了自己和他人的bug,我好气啊~~)。

  上周五与一位真·大佬聊天,他告诉我测评机的数据过于随机,可能找不到潜藏较深的bug,看来以后,我也需要读一读同组同学的代码,取长补短的同时,也可以顺便找找bug(究竟哪个是顺便😊)。

    注:特别鸣谢何岱岚同学,让菜鸡拥有了搭建测评机的可能。

电梯载人策略图示

SOLID自评


SPR(单一责任原则):几乎实现了一个类只负责一项工作,但其实也可以将这些类进一步细化,划分成更多的小类,每个类负责更细化的工作。

OCP(开放封闭原则):感觉还可以,但可拓展性尚需进一步加强。

LSP(里氏替换原则):没有用到继承(线程类除外)。

ISP(接口分离原则):没有用到接口。

DIP(依赖倒置原则):感觉做的不太好,构架的抽象程度不充分,仍然有面向过程的影子。

写在最后

心得与体会

  多线程其实蛮有趣的(如果它们不是那么不听话的话),这也是我们将来走上工作岗位的重中之重,经过了两个单元,我也渐渐的适应了OO和OS的双重夹击(即天天熬夜);  //为将来996做好了身体上的准备

  虽然很累,但是真的学到了不少有用的知识和技能,提升了自己的核心竞争力;  //离996岗位更进了一步

  第三次作业有幸分到了A组,见到了许多大佬的代码,让我更加领会到了面向对象的含义和优秀的架构,今后我会继续学习大佬们的代码风格,争取可以活(xian)学(xue)活(xian)用(mai)。

  虽然不知道下次作业是啥,但肯定和多线程脱不开干系,继续努(ao)力(ye)吧。

愿指引明路的苍蓝星为我们闪烁