oo第二次博客
前言:
这是一篇面向对象作业总结,作业内容是模拟电梯调度,一共有三个阶段,具体要求不详述,第一阶段只要求先来先服务电梯,第二次支持捎带,第三次则需要多部电梯协调,通过换乘来完成请求。本次作业在优化方面效果不佳。设计比较统一,设计原则检查放在最后。
第5次作业
类图如下:
说明:
具体的来说,M是主入口,也是主线程,目的是获取请求。Loader是容器,也只存在一个单例ld,其中req表是其管理的实质,当要synchronized多个实例时,loader优先上锁以防死锁。DianTi则是从属线程,目的是模拟电梯的运行。
电梯会向loader获得第一个请求,如果没有得到就循环,直到M放入请求时唤醒它,如果获得到请求,则先到那个层,然后取得所有当前层乘客,接下来先向上走再向下走,每一层询问ld有无请求并决定开门与否,其中管理in队列以获得请求,直到in队列空,开始下一轮。轮询而没有wait()。
这里涉及到一个监视器,就是结束标志,它封装于loader内部的mainEndFlg,当M结束时设置mainEndFlg,电梯如果没得到请求又发现M已经结束,则将自己结束了。
类图中的另外两个是用于debug的,模拟测评机进行输入按时投射。
代码度量结果如下:
从度量结果中可以看出,我没有用子类,这次环复杂度没标红,而是嵌套块深度深了点,主要体现在dianTi的run方法,其抽象还不够。
Bug:
在强测时出现了bug,原因是轮询之前没有将进行goup()和godown(),乘客被锁在电梯里。
第6次作业
类图如下:
说明:
本次作业与之前一次作业改动不大,具体来说就是添加了一层过滤输入,使得楼层内部表示是-2,-1,0,1~16,输出楼层的修正是在dianTi内部函数完成,因为输出必然只有dianTi才能控制。
第二点就是wait和notify。本次作业新增了nextTo,作为一个监听器,当没有请求时电梯通过nextTo.wait()进入睡眠,当有请求来到ld时,nextTo.notifyAll()唤醒所有进程。这些都封装于loader内部。
优化不理想的原因在于每次电梯询问请求时ld都将其指向人数最多的一层,这在随机情况下统计特性(可能)不如走到最近一层。
度量分析:
这一次我将dianti的run方法进行了一点抽取,所以发现没红。7个静态方法都是一些辅助debug和计算max、min、abs的程序,总方法比之前多了,变为52个。
环复杂度最高的时goDown和goUp,这两个是功能性方法,摊平有助于修改。不过它们确实是细粒度的并行。
Bug:
第一个bug就是因为没有考虑0层不可达,通过添加一个类解决之。第二就是CPU轮询时间太长,通过wait解决之。第三个bug没体现出来,留在下一次作业中。
第7次作业
类图如下:
说明:
这次作业需要考虑多部电梯协调、不可达楼层以及乘客换乘的问题。这里加了一层reqDistri,也是一个线程,其实也可以省略,因为计算量并不算太大,它存在的目的是为了对M的输入进行处理,由乘客请求得出一条可达路径,插入ld队列。
一开始使用弗洛伊德算法,发现最多两次换乘,后期加入preSche层,原理是获得每个电梯当前状态的临时展板dianTiData,然后由起点和终点之间依次尝试插入每一层,用一些启发式的方法决定路径。
加入pair辅助类,拓展了myPerson的内容,使之涵盖了一条请求路径,但是没有直接将其分给哪个电梯,ld是被动等待请求的,电梯则是随机获得请求的,通过判断请求路径的第一个pair是否是自己可达的来决定让不让乘客上电梯。当有电梯去往某一层时,下一个电梯则询问ld则自动跳过那一层,以期望电梯分散搭载乘客。
停止条件方面,将mainEndFlg放在reqDistr里,reqDistr在M结束后就结束了,而电梯则是需要所有电梯都结束且不可能再有请求时,才能结束。所以ld还要访问所有in队列看看其是否为空,每个电梯通过ld的isEmpty方法知道别的电梯的in是否空,以及ld是否还会再有请求。
后期为了优化,在每个电梯层数变化时,强行更新ld.req里所有请求的路径,并通知所有的电梯,以期望电梯能够更加智能。这样会对性能有一定提升。
度量结果如下:
可以看出还是没有红色的,不过总代码行数增加了许多,主要是尝试了两种启发式的方法,一种就是最短距离的电梯先到,不考虑上下行走状态,另外一种是考虑上下行并且上下行到底就会等待。其实是第一种的性质比较好,而第二种的假设比较多,如果真正实现了完全预运行,则性能应该会更好,望下次改进。要完成这些,需要一个状态展示板,牵涉到了很多新增属性。
Bug:
如果测试到了需要wait的时候再次进行goUp(),goDown(),然后切换cpu发现有新的请求入队,接着电梯进入wait模式,然后发现没有更多的请求去唤醒它,这样它们就死等下去。解决方法就是再次判断是否需要wait,并且wait时间不超过5秒。这样对cpu还是可以接受的。
设计原则检查
Single Responsibility Principle:mainEndFlg和nextTo两个监视器可以抽离出来,其余基本是专注于管理自己队列的程序。
Open Close Principle:大架构基本不变,有些地方需要加点代码重构,而更多的功能性要求则是通过添加方法和添加类来实现的。
Liscov Substitution Principle:根本没有子类,不用替换了。
Interface Segregation Principle:没有接口
Dependency Inversion Principle:抽象的就abs,getlock和release这些static的方法,其他都是源于细节的构架。所以抽象依赖无从谈起。
总结
觉得多线程最重要的就是生产者和消费者模型,worker thread是其变种。所以有多线程的地方首先看其buffer是什么,有几层buffer,在一些对性能要求比较高的图形学架构上就使用这种多层流水的模式,也是生产者消费者模型的变种。
轮询可以交给一个特定的进程(比如jvm)去做,虽然还是要轮询检查时间,但是毕竟需要轮询的线程少,则轮询效率更高,线程则用wait的机制等待时间就好。
缺点在于,没有很好的预模拟程序,并且电梯没有一个预请求队列,每个电梯都是随机抢到哪一层的,所以性能得分不高。