(一)三次作业的要求:

   作业一:傻瓜电梯,每次请求队列中的第一个,甚至不需要多线程

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

   作业二:捎带电梯,如果在前往某一层的路途中有可以捎带的人,捎带

   作业三:三部电梯,每部电梯能停靠的楼层不同,所以可能需要将人放在某一层换电梯

(二)我的大致架构:

   作业一:一个线程“A”随时随地读入请求,另一个线程“B”模拟电梯的运行,一个类,里面存放公共资源及公共资源的访问方法,线程“B”的查询请求队列、获得请求、“wait”、“notifyAll”都调用这里面的方法

   作业二:结构同作业一,处理方式稍有不同

       类图:

       面向对象第二单元三次作业总结 随笔 第1张

       面向对象第二单元三次作业总结 随笔 第2张

 

       各方法:

       面向对象第二单元三次作业总结 随笔 第3张

 

 

   作业三:四个线程,一个为读请求、另外三个模拟三部电梯,一个类存放公共资源,电梯的查询请求队列、“进出人”、“wait”、“notifyAll”等的行为都调用这个类里面的方法。

       类图:

       面向对象第二单元三次作业总结 随笔 第4张

       面向对象第二单元三次作业总结 随笔 第5张

 

      各方法:

       面向对象第二单元三次作业总结 随笔 第6张

       面向对象第二单元三次作业总结 随笔 第7张

       面向对象第二单元三次作业总结 随笔 第8张

 

       关于solid原则:

        单一职责原则:很糟糕,Source类承担了所有的电梯行为和公共资源的管理,几个电梯线程(ElevatorL类)只是伸手党,只形式上变更楼层号、电梯运行状态。

        开闭原则:还算遵守了,会被公共访问的资源、方法都至少锁好了,没被锁的也能保证不会被不该访问的访问

        里氏替换原则:只有“extends Thread”,另外由于我的两个循环里的电梯的行为非常相似但有不同,应设计一个接口

        迪米特原则:这简单的种结构还算是遵守了吧

        接口分离原则:菜炸了,后期完全成了复制、粘贴、微改

        依赖倒置原则:“高层不依赖于底层”遵守了,可细节处理炸了——整体逻辑就没有过关

 

(三)一点设计上的细节的处理:  

   作业一:两个线程都要访问一个公共资源:请求队列。一个线程(设为“A”)是把输入添加到请求队列里,一个线程(设为"B")是从队列中取出第一个请求并删除这个。两者势必为“写写冲突”。所以我用了wait+notifyAll,主要是,A每添加一个请求就“notifyAll”,而B每次在队列不为空时开始运行(否则"wait"),由于B每上升一层需要0.4秒、开关门共需要0.4秒,所以我用“wait(400)”并循环凑足这0.4秒(A可能在B还没等到0.4秒时就唤醒B),在这个wait的过程中A就能读写请求队列了。

       另外可能需要注意的是,输入终止时,如果B仅仅是在“请求队列为空”时保持“wait”,那B就永远不会结束了,所以可以再设一个AB共享的标志比如“end”,end为true时B可以一直wait,当输入终止时A将end改为false。那这时就又要注意B可能在请求队列里还有人时就结束了——我用了一个很菜的办法:就是当end为false时B从“while(true)”这段中跳出来,再将end设为true,再以“请求队列不为空”为循环条件进行循环运行,所以当请求队列为空时就跳出了循环结束线程了。总的来说,我是将B的运行分为两个阶段,第一个阶段是由于随时都可能有新的输入(不管过了多久),所以B永远不能停,在当前的请对队列为空时B就要保持“wait”状态,循环的条件为“true”(永远循环),第二个阶段是当前不会再有新的输入了,只需要把请求队列中剩下的处理完,所以循环的条件就为“请求队列不为空”

   作业二:相比于作业一,由于增加了“捎带”,所以电梯在运行过程中在每一层都要查询一遍请求队列有没有能捎带的,或是有没有下电梯的,有就要开门,由于开门关门一共会有0.4秒,那么在这时可能会有新的请求进来(电梯也用"循环wait(400)以凑足0.4秒”),所以关门结束的前一瞬也要读一遍请求队列有没有能捎带的。然后当输入终止时,不仅要判断请求队列是否为空还要判断电梯里的人是不是都送出去了。

       另外在这里我遇到的坑点是有关电梯的第一个请求:

         当电梯从“请求队列为空、输入未终止时的‘wait’状态”到“请求队列有了一个请求后电梯开始运行”时对这个请求的处理——尤其电梯当前不在这个请求的“from”楼层时——是将其先“放入“电梯好呢,还是先将电梯运行到它的“from”楼层再将它放入电梯中。对于后面一种方式,我认为如果电梯有容量限制的话(这次作业没有),那么当其到达“from”楼层时可能会因为中途捎带了太多而不能将初始的请求放入了——当然,完全可以设置电梯运行到“from”楼层前以“电梯真实总容量-1”的容量来判断中途捎带不捎带(超载与否),另外如果是多部电梯的话,可能自己到“from”楼层时请求已经被其它电梯处理了。我用的是前一种,即先假装将这个请求放入电梯中,电梯再运行到“from”楼层,由于电梯在每一层都需要查询有没有要进来或出去的,我就增加了已经在电梯“里面”的那些请求“有没有要进来的”——当然只能是电梯里的第一个请求。再是一点细节:判断有没有要出去的的时候,可能第一个请求还没真正进来而已经运行到了它的目的楼层,不处理的话就会出现“第一个请求还没有进电梯却从电梯里出来了”现象。

    作业三:我给三部电梯每部都设了一个私有动态数组,记录当前这部电梯里面的请求。我是用的也是捎带,即电梯在运行到某一层时判断有没有要下的、有没有要上的——但这次作业设置了每部电梯的容量,以及每部电梯能停靠的楼层,三部电梯之间还可能需要合作来处理一个请求。所以我新增加了一个公共资源:“二次请求队列”,里面专门放的是需要转乘的请求,比如a请求最开始是由电梯1处理的,但电梯1不能在a请求的目标楼层停靠,所以电梯1就在一个“合适的楼层”将a放下,并更新a请求的“from”楼层、将a请求放入 二次请求队列 里。那什么是“合适的楼层”呢?我是这样规定的:

       由于有些电梯里的有些请求的目标楼层电梯不能停靠,所以电梯是在“距这个请求的目标楼层最近的自己能停靠的楼层”将其放下、塞进“二次请求队列里”。(所以在每一层判断有没有要下的请求时也要看是不是到了“最近的楼层”、即不仅仅是“是不是到了请求的目标楼层”

       所以电梯在处理或捎带一个请求时就要判断自己当前是不是已经处在了这个“最近的楼层”,如果是,就不处理或捎带这个请求。 

       所以,我规定一部电梯在查询有没有能捎带的请求时(前提:电梯还有空位,否则不查询)依据这个原则:

         大前提:请求的“from”楼层是当前电梯所在的楼层:

         “二次请求队列”中的能到达其目的地的请求优先,“一次请求队列”中顺路的且能到达其目的地的请求优先,“一次请求队列”中顺路的但不能到达其目的地的且当前不是“最近楼层”的请求优先。如果有其他的就不捎带。

      另外,可能需要注意的坑点是:在输入终止后,可能每部电梯里面都还有人,且可能还需要三部电梯合作,所以每部电梯不能仅仅是将自己里面的人运完就结束了,还要等三部电梯都将自己里面的人运完后,看“二次请求队列”里面有没有自己能运行的请求——这时候之后要判断“能否到达请求的目标楼层”就行了,不需要判断“能否到达请求的‘最近楼层’”,如果这时候没有就能真正“下班了”,如果有,那就要开始运行。这里可以给每部电梯设一个“标志位”,不论是谁的“标志位”其他电梯都能访问到,且自己设置自己的“标志位”,当输入终止且自己里面的人已经运行完了,且查一遍“二次请求队列”和“一次请求队列”后发现没有自己能帮上忙的,就退出这个“while(true)”循环(同作业二),将自己的“标志位”设为“false”,并判断其它两个是不是也已经为“false”了,如果不是,就进入‘wait’,等待最后一个出来的电梯线程将自己唤醒,如果是的,就“notifyAll”,开始“while(“二次请求队列”不为空)”看有没有自己能帮上忙的,没有就真·结束了。

(四)出现的bug

     作业二找到的bug都是逻辑上的bug,因为考虑不周导致有的请求没有进入电梯却在其目的楼层处出来了,主要bug是出在处理“第一个请求”时、电梯里的主请求和捎带请求都处理完了开始运行掉头处理第二波请求时电梯新的目的地、运行方向的变更。

     作业三的bug也都是逻辑上的。有出在电梯处理第一个请求时(后悔没有在之前de完作业二的bug)的什么“在前往第一个请求的‘from’楼层时若经过了它的目标楼层不要把它‘out’出去了”、判断当前是否为“最近的楼层”时的——因为可能有上下两个“最近的楼层”这些很零碎致命的bugs,以及判断当前是否为“最近的楼层”时因为可能有多个而导致的死循环——有时候可能因此不捎带、有时候却可能因此捎带、然后疯狂开关门疯狂让其进出。

(五)hack手段(没有手段)

     hack的数量很少,一两个,还是趁别人“sleep(200)”时hack的

     有点后悔到现在还没进行过工程化hack,看自己被炸一堆数据、手动分析别人的代码以及被别人炸真是太难受了,绝对要掌握这种debug方法。

 (六)心得

     这单元作业是主要针对多线程的,我在写第三次作业时就有点好高骛远,时间紧、线程安全都还没保证就想着优化——其实也不是什么很好的优化,就是保证两个循环里面都是捎带ALS原则,结果写出来后de线程安全的bug及其麻烦,到处是注释掉的“System.out.println”。另外,一定要留出充裕的时间写,留出至少一天来debug、做优化。

     关于线程安全,我觉得只要只要分清楚各线程对各种公共资源会在什么时候、什么地方进行什么样的访问就能避免一些常见的死锁现象了。

  

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄