oo第二单元总结
写在前面:
本单元的主题是”电梯“,从第一次普通的电梯到第二次可以横向移动的电梯到第三次随便走的电梯。本单元相对于第一单元,减少了重构次数。第二三次作业都是在第一次的基础上进行迭代开发。(体现出架构选择的重要)同时也有被线程安全问题所折磨。总的来说主要进行了多线程开发,电梯调度算法(没有想象中那么可怕)。
总体架构设计
设计模式
总体设计模式采用了生产者-消费者模式,即生产者和消费者通过一个内存缓冲区进行通信,生产者生产消费者需要的资料,消费者消费生产者提供的资料。此处的内存缓冲区就是调度器,用于存放Arraylist<PersonRequest>。
在hw_7采用了"流水线模式",即经过多道模式进行产品。通过动态解析指令流水执行到person执行完毕。
本来还想用观察者模式,但是偷懒了。
主要类的设计
主要包含了以下几个类:
Elevatormax 类里面啥也没有,仅是作为横向电梯和纵向电梯的父类。便于对电梯的统一处理。理论上应该提出横向电梯和纵向电梯共有的类以及方法,但是第二次作业直接复制粘贴然后对横向电梯的调度进行了修改,导致代码的重复量较大,不好。
Elevator顾名思义,在电梯单元里当然elevator类是最重要的。电梯从调度器中得到personrequest,而当多个电梯共享一个调度器时采用随机的方式,只要电梯满足乘客的需求就会进行抢夺。在elevator类中主要就是实现调度算法。对于调度算法呢其实没有一个完美的,在不同的场景下不同的调度算法有各自的优势,但是在统计情况下还是能选出较高的适应度的算法。本人不太卷,所以就采用了ALS,最后看起来性能也不慢?有些特殊的场景还能得满,但是有些测试点就只能作为最慢策略。主要思想就是当电梯中没有主请求的情况下先去接主请求,然后在送主请求的路上实现可捎带,当主请求out之后在重新指定主请求。其实电梯实现的就是接送主请求。
Operate调度器,其实就是一个存储区,Input将personrequest放到operate中。在我的实现中,每一层,每一座都有一个operate,而每个operate对应着很多个电梯,电梯从operate中争夺资源。当然采用一个operate也可以,通过电梯来判断能接收的personrequest。
Input,作为生产者向对应的operate中发送personrequest,以及增加的电梯信息。
一个典型的生产者消费者模式就建好了。
input->ArrayList<operate>->Elevatormax
Factor,按照指导书的建议采用一个factor类对电梯的请求进行封装,但是并没有完全封装,实际上是建立了一个类来实现新增电梯的分配。
具体作业的实现
hw_5
第五次作业在这几次作业中可以说是花时间最多的了,因为一个新概念多线程的加入。
多线程好是好,不用等一个线程结束在运行另一个线程,充分的利用时间,解决了cpu等待数据输入时的大量时间浪费,提高cpu的利用率但是也带来了线程安全问题,而在我们的实验中主要解决的就是线程安全问题。线程的安全问题主要体现在写入和读出的不相匹配,输出时的资源抢占。
我的理解在学习了多线程之后,只要没有锁的限制,那么在一个线程执行当前代码的同时,就要考虑到其他线程会不会抢占cpu资源,会不会产生线程安全问题。如果会产生资源竞争就要分析产生的结果,为了避免看到不想要的结果,加锁。但是锁也不能乱加,首先要注意防止死锁,然后再注意要保证能读到ArrayList<operate>。emmm这么说可能不好理解,就比如在电梯的调度中给这个方法全加锁,电梯运行有时间损耗,而在这个时间里有新的请求加入,operate更新,但是由于此时电梯还在运行中,operate其实是不能更新的(被锁在门外),导致性能贼低。
hw_6
第六次作业是在第五次作业的基础上迭代开发的,其实没啥区别,本质上就是加了几个调度策略不同的电梯而已。所以本着能偷懒一点是一点的原则,我直接复制粘贴了第五次作业的竖向电梯类,因为需要考虑的问题都是一样的。所以hw_6主要考虑的点就在调度策略上,还是以als作为标准写,同样有一个mainrequest的概念,题目就可以简化为接送mainrequest,在路上顺便接人,mainrequest出去后重新指定。和第五次作业在架构上区别不大。只不过为了方便处理电梯让他们有一个共同的父类elevatormax。
hw_7
第七次作业,只要用一个流水线处理就可以解决1-FROM-A-1-TO-C-10这种问题。我的方法是首先对请求进行判断,假如是直上直下的就像hw_6那样处理。而左右移动的电梯,这次作业的不同点在于,不是每一个电梯都可以到达每一层的任意一座。所以不能简单的判断只要有横向电梯就直接移动。而是需要首先判断当前楼层的电梯能否到达(假如能到达直接加入到当层的operate中),如果不能就遍历所有的电梯(每一个横向电梯有一个专属的到达区域),看哪一个适合接,然后将当前请求加入到当前楼层的调度器中。横向电梯在接的时候也要经过特殊处理,只有可以能上能下才会激活电梯移动然后随机移动(保证电梯可达)。然后写好了横竖电梯之后,只需要将一个首尾楼层、座都不相同的需求加入竖向电梯移动,移动到哪层要遍历电梯确定。然后竖向电梯送达乘客后判断乘客是否到达终点,如果没有那么就更新请求为 当前座-当前楼层-目标座-目标楼层,然后加入横向电梯,横向电梯移动到目标层后更新请求为一个竖向请求,再加入竖向电梯就可。
本次作业还有一个小点跟hw_5和hw_6不一样就是前两次作业我们可以采用当scanner.nextLine == NULL来给电梯进程发送结束信号,电梯等到处理完请求收到end信号就会从while(true)中break出去,但是这次作业由于采用了流水线模式,我们不能简单的这么判断,因为这样只能确保每个request经过了一次电梯处理,而生成一个完整的产品最多需要三次。这显然不能解决我们的需求。我们可以借鉴训练代码的方式实现一个类似Semaphore的信号量,有personrequest进入就acquire一下,处理完了就release一下,然后如果scanner.nextLine == NULL 就判断semaphore permits是否为0,不是0的话就等待。或者可以采取一个共享变量来计数,同时也要注意线程安全。
UML协作图:
BUG
1.hw_5中有两个巨大的bug,第一个就是我先输出了arrive在sleep,莫名其妙过了中测。。。感谢助教最后没改强测的评价机制。还让我狗了0.4s,最后互测23/23圆满结束。另一个bug就是输出的时候没考虑线程安全的问题,最后用Elevator.class进行加锁。
2.hw_6死等了,开始怀疑评测机错了(这是不可能的,因为本地咋也复现不了)然后读代码很容易就发现了死等的场景,由此可见找bug要沉下心来,不能浮躁。
3.hw_7强测崩了一个点,原因出在了调度算法上,因为mainrequest的运行方向可能会随着新增电梯的加入而改变,当时没注意到这一点。
4.hack策略主要从线程不安全入手,考虑自己出现的线程安全问题。
单元总结:
首先从正确率上相对于第一单元提高了很多,第一单元是CBA,这单元应该是AAA,也算一个进步(不过也应该是电梯bug不好找的原因)。
然后在本单元加入了多线程,加入了多种设计模式的应用。多线程虽然很玄学,但是不要浮躁,只要耐下心分析代码,并且掌握Thread的调试方法,其实bug也是很容易找到的,切不可埋怨评测机。