关于第二单元多线程电梯总结博客
多线程控制
在本单元的三次作业中,我的多线程的架构思想都是一样的。即由两类线程和一个中间容器组成。
Receipt Requests 线程:
程序的Main线程,通过while true轮询的方式读取接口产生的请求。并传递给Outdoor Container。
Indoor Elevator 线程:
模拟一个电梯,主要包含电梯的楼层信息,电梯内空间信息和电梯的身份。电梯会在到达某一楼层主动去Outdoor Container的相应楼层抓取请求。
Outdoor Container 对象:
模拟的一个楼层的电梯门口的空间,请求从Receipt Requests和Indoor Elevator都有可能在其中添加请求。而Indoor Elevator每次都会在其中抓取。
基于度量的程序结构分析
本分析使用的是基于IDEA上的的插件Metrics Reloaded
对于其分析项目的含义做一个简单解释
-
ev(G):即Essentail Complexity,用来表示一个方法的结构化程度,范围在\([1,v(G)]\)之间,值越大则程序的结构越“病态”,其计算过程和图的“缩点”有关。
-
iv(G):即Design Complexity,用来表示一个方法和他所调用的其他方法的紧密程度,范围也在\([1,v(G)]\)之间,值越大联系越紧密。
-
v(G):即循环复杂度,可以理解为穷尽程序流程每一条路径所需要的试验次数。
对于类,有OCavg和WMC两个项目,分别代表类的方法的平均循环复杂度和总循环复杂度。
作业一
类,方法的结构:
度量:
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
my.ElevatorControl.ElevatorControl(ElevatorRoom) | 1.0 | 1.0 | 1.0 |
my.ElevatorControl.moveOneLevel() | 5.0 | 2.0 | 7.0 |
my.ElevatorControl.openAndClose() | 1.0 | 5.0 | 5.0 |
my.ElevatorControl.run() | 1.0 | 3.0 | 4.0 |
my.ElevatorRoom.addindoor(PersonRequest) | 1.0 | 2.0 | 2.0 |
my.ElevatorRoom.addoutdoor(PersonRequest) | 1.0 | 2.0 | 2.0 |
my.ElevatorRoom.endTask() | 1.0 | 1.0 | 1.0 |
my.ElevatorRoom.indoor2out(int) | 1.0 | 3.0 | 3.0 |
my.ElevatorRoom.isindoorEmpty(int) | 1.0 | 1.0 | 1.0 |
my.ElevatorRoom.isoutdoorEmpty(int) | 1.0 | 1.0 | 1.0 |
my.ElevatorRoom.isTaskCondition() | 2.0 | 3.0 | 4.0 |
my.ElevatorRoom.outdoor2indoor(int) | 1.0 | 3.0 | 3.0 |
my.Main.main(String[]) | 3.0 | 3.0 | 3.0 |
Total | 20.0 | 30.0 | 37.0 |
可以看出,这次作业的以上各个参数比上一单元的作业有了较大的提升。因为本次的题目就是建立在一个低耦合的基础上,且由于只有一部电梯,所以并不需要考虑复杂的情况,只需要做好获取请求,存储请求,提取请求,执行请求,就可以实现。
值得一提的是,这个单元的作业中,我并没有安排调度进程,而是在电梯内部布置了移动算法。并且移动算法只提供上升,下降,不动三种信息。这样的好处是电梯可以看作一个完全的对象,使整个程序更加低耦合,更加不容易出错。
作业二
类,方法的结构:
度量:
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
my.ElevatorControl.alsmoveOneLevel() | 9.0 | 15.0 | 7.0 |
my.ElevatorControl.ElevatorControl(ElevatorRoom) | 1.0 | 1.0 | 1.0 |
my.ElevatorControl.moveOneLevel() | 5.0 | 9.0 | 3.0 |
my.ElevatorControl.openAndClose() | 1.0 | 1.0 | 1.0 |
my.ElevatorControl.run() | 1.0 | 3.0 | 3.0 |
my.ElevatorRoom.addindoor(PersonRequest) | 1.0 | 2.0 | 2.0 |
my.ElevatorRoom.addoutdoor(PersonRequest) | 1.0 | 3.0 | 3.0 |
my.ElevatorRoom.endTask() | 1.0 | 1.0 | 1.0 |
my.ElevatorRoom.indoor2out(int) | 1.0 | 3.0 | 3.0 |
my.ElevatorRoom.isindoorEmpty() | 1.0 | 1.0 | 1.0 |
my.ElevatorRoom.isindoorEmpty(int) | 1.0 | 1.0 | 1.0 |
my.ElevatorRoom.isoutdoorEmpty() | 1.0 | 1.0 | 1.0 |
my.ElevatorRoom.isoutdoorEmpty(int) | 1.0 | 1.0 | 1.0 |
my.ElevatorRoom.isTaskCondition() | 1.0 | 4.0 | 4.0 |
my.ElevatorRoom.out2inandin2out(int) | 1.0 | 7.0 | 7.0 |
my.ElevatorRoom.outdoor2indoor(int) | 1.0 | 3.0 | 3.0 |
my.Main.main(String[]) | 3.0 | 3.0 | 3.0 |
Total | 31.0 | 59.0 | 45.0 |
Average | 1.8235294117647058 | 3.4705882352941178 | 2.6470588235294117 |
从之上的数据可以看出,电梯的移动算法的深度很高,这是因为这个算法需要考虑很多算法所以复杂度比较高。并且这个的判断需要很多条件,所以耦合度也比较高。
由于我在第一次电梯作业中已经使用了较为健全的架构所以,第二次作业主要工作都在cpu运行时间的控制上。由于只有一部电梯,所以我只需要在请求队列为空的时候把电梯线程wait,在有新的请求进去后就把其唤醒。实现这一点并不困难,我仅仅在请求输入方法加入notifyall,在请求提取执行方法前判断是不是空,如果是空就wait。
关于调度算法,我并有完全使用als,一方面是我的架构并不能很科学的支持这个算法,另一方面在这次作业中由于是单电梯,且性能标准是完成调度任务总时间,所以我采取了als的第一部分(空时方向的判断),而捎带为不判断乘客的方向直接捎带。
作业三
类,方法的结构:
度量:
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
my.ElevatorControl.als() | 7.0 | 12.0 | 19.0 |
my.ElevatorControl.ElevatorControl(ElevatorRoom,String,int[],int,int,int,int) | 1.0 | 1.0 | 1.0 |
my.ElevatorControl.moveOneLevel() | 2.0 | 3.0 | 5.0 |
my.ElevatorControl.openAndClose() | 6.0 | 8.0 | 11.0 |
my.ElevatorControl.run() | 1.0 | 3.0 | 3.0 |
my.ElevatorRoom.addoutdoor(PersonRequest) | 1.0 | 2.0 | 2.0 |
my.ElevatorRoom.alloctolevel(String,PersonRequest) | 14.0 | 11.0 | 14.0 |
my.ElevatorRoom.endTask() | 1.0 | 1.0 | 1.0 |
my.ElevatorRoom.getoutdoor() | 1.0 | 1.0 | 1.0 |
my.ElevatorRoom.in2out(ArrayList,int,int,String) | 1.0 | 5.0 | 5.0 |
my.ElevatorRoom.isTaskCondition(ArrayList,String) | 2.0 | 8.0 | 12.0 |
my.ElevatorRoom.iswait() | 1.0 | 3.0 | 3.0 |
my.ElevatorRoom.out2in(ArrayList,int,int,String) | 2.0 | 6.0 | 9.0 |
my.Main.acc2level(int) | 1.0 | 1.0 | 1.0 |
my.Main.getaccess(String) | 5.0 | 2.0 | 5.0 |
my.Main.main(String[]) | 3.0 | 3.0 | 3.0 |
my.Prl.getAtLevel() | 1.0 | 1.0 | 1.0 |
my.Prl.getPersonRequest() | 1.0 | 1.0 | 1.0 |
my.Prl.getToLevel() | 1.0 | 1.0 | 1.0 |
my.Prl.Prl(PersonRequest,int,int) | 1.0 | 1.0 | 1.0 |
Total | 53.0 | 74.0 | 99.0 |
Average | 2.65 | 3.7 | 4.95 |
从上表格可以看出,als和alloctolevel的复杂度最高。其中als复杂的原因同上。由于第三次电梯可能需要多次运输同一个任务,所以我在任务进入电梯时候会重新分配一个目标楼层,其标准是最接近请求目标楼层且该电梯能到达的楼层。因此,该分配机制是根据电梯进行了判断导致情况复杂,所以该函数复杂度高。
对于多次分配的思考:根据题意可以分析出,任意两次运送都能完成请求。所以我对PersonRequest进行了封装。增加了所在楼层和目标楼层。所在楼层由当前楼层决定,目标楼层在进入电梯时分配。因此实际情况就变成,电梯把请求送到附近的楼层,然后送出。之后某一时刻另一个电梯可能经过这个位置,并把这个请求送到最终位置。
bug分析
自己的bug
这个单元作业并没有产生bug
程序设计的问题
- 对于三个不同电梯停靠位置不同采取的硬编码方式。导致函数复杂死板,可扩展性较差。
- 直接使用的arraylist一维存放请求,导致复杂度较高,每次都需要遍历。
发现别人bug的策略
- 自动随机数生成测试数据。
- 用python的subpipe创建新的进程,使得定时投递。
- 由于本人三次都在A组,这样的测试方法并没有测出bug。
认识感想
第二单元的作业是我们第一次写多线程程序。在完成这次作业中我遇到了很多挑战,比如多线程的锁定,对象的共享。在分析和构建的过程中,我们对消费者和生产者的关系有了更加深刻的认识。在使用多线程程序中,我深刻感受到了多线程能有效的提升程序的效率和功能。
在线程安全方面,我认为主要在两方面做好功夫。
一是分析什么是共享变量,在同时操作时如果对共享变量进行了读写,就需要上锁。并且可以使用原子变量等性质保持对象的原子性。
另一方面是分离共享和非共享部分,我采取的策略是把共享的请求队列和一些调度信息都打包放在调度的对象中,在上锁的时候直接锁住this部分,就可以化繁为简。