BUAAOO 第二单元
作者:互联网
第二单元OO总结
Part1. 代码构思设计
-
输入线程(input):只要有输入,便会交由调度器,增加电梯外等待队列。当输入停止后,输入线程直接结束。
-
电梯线程(elevator):不断运转(即正常的接人送人操作),若电梯内外都没有人且输入停止,则电梯线程结束。
-
调度器:
HW5 :调度器内管理电梯队列和电梯外的等待队列,同时管理电梯移动的策略方法、在每层停留时电梯内外的队列增减人时的方法。
HM6 、HM7 : 将本来由调度器管理的队列交由电梯自己管理,调度器只负责将输入线程传入的需求按照一定的策略分配给特定的电梯管理。每个电梯都有自己的等待队列和电梯内队列,并且配有管理电梯移动的策略方法,电梯如何移动完全交由电梯自己处理。
Part2. 同步块与锁
1. 同步块
-
同步块:
-
同步块设置意义:所有同步在一个对象上的同步块在同时只能被一个线程进入并执行操作。所有其他等待进入该同步块的线程将被阻塞,直到执行该同步块中的线程退出。
-
同步块在作业中的应用:
从part1的设计可以看出,我的构造内涉及到同步对象的只有两种情况:调度器分配请求给电梯,改变电梯的等待队列;电梯停留在某层,进行接人和送人时电梯队列的改变。
-
2. 锁
-
同步控制机制
-
monitor机制
-
lock机制
-
-
上锁原则:务必保证拿到同一把锁
-
锁的类型:
-
ReentrantLock:可随处使用
-
ReentrantReadWriteLock:单写、多读
-
Read Write Lock等
-
-
我的选择:
我在作业中采用新建一个锁类,并且生成static锁对象,用同一把锁锁住了所有的同步对象。因为加锁不当容易产生死锁,而且在本次作业中需要保护的共享对象仅有电梯等待队列,所以采用一把锁已经足够。同时采用copywriteArrayList这种线程安全的容器用来盛放队列,减少了需要加锁的地方。
3. 锁与同步块语句关系
-
方法一: 确定同步块内的共享对象,将其作为锁住的对象,即限制访问该对象的线程数量。
-
方法二: 直接新建锁类和锁对象,将代码区锁住,限制访问该代码区的线程数量(这也是我作业当中采用的方法)
Part3. 功能&性能设计
-
HW5
可以看到电梯线程和输入线程之间是通过调度器进行连接,输入线程添加调度器内等待队列的人数,电梯线程减少(通过运输)等待队列的人数。
-
HW6+HW7
在后两次作业中,调度器不再管理队列,只负责决定处理来自输入线程的请求到底应该分配给哪个具体的电梯。
额外的,正如下图所示,最后一次作业考虑到了换乘,所以调度器还负责处理标记乘客信息,如果需要换乘,那么置标记位为1。
-
扩展性分析
这次的作业我个人认为扩展性做到较好,每次代码改动量不超过100行,这也得益于清晰的结构划分和功能分配。
模式采用生产者-消费者模式,所以明确生产者为输入线程,消费者为电梯线程,调度器只是中途容纳请求的容器(顺便将生产者的请求划分出来,分别给予不同的消费者)。
同时,将电梯各个操作分别封装为方法,比如电梯的上下移动、乘客进出、电梯移动策略等。
在之后的迭代中,第二次作业增加了电梯数量,这只需要设置添加几个消费者线程同时start即可。第三次作业增加了换乘和多类电梯,只需要修改电梯的特性和增加调度器对请求的处理操作(如标记是否换乘)即可。
Part4. 分析bug
-
HW5
第一次强测挂了两个点,都是源自没写morning模式并且LOOK算法写错了,导致数据超时。自以为运行时间不会有太大差别,但没想到会直接tle。所以人还是尽量不要偷懒QAQ,多分析一下输出数据是不是预想的那样,而不是看到输出正确就草草了之。
-
HW6、HW7
这两次作业强测都过了,只不过性能分有所差异。因为第一次作业做完后我就推迟了bug修复,直到第二次作业都截止了我才发现LOOK算法有问题,当然已经来不及了。第三次作业修改后性能分也提升了,总的来说,修复bug应该尽量提前,不然就是为后面的作业挖坑。
Part5. 分析别人bug策略
-
hm7
这次作业是最容易产生bug的,因为很多人在实现换乘时往往会出错。所以不妨多多设置同一时间多个人到来,并且都需要换乘的数据。
Part6. 心得体会
-
线程安全
最开始接触线程安全时,总觉得难以下手,不知道何时让线程开始,如何让线程结束,在过程中如何加锁,如何设置wait和notify。通过这三次作业,我也有了一点小的心得。
首先,线程的开始并不需要太介意什么时候开始,可以设置一个Init函数,固定进行start线程。
其次,线程的结束取决于终点判定,在run方法内的while(true)设置好break的判定条件即可。
synchronized (Lock.getLock()) { while (waitQueue.isEmpty() && inQueue.isEmpty()) { if (exQueue.isEmpty() && Input.getEnd()) { break; } Lock.getLock().wait(); } }
比如这段代码便是对电梯线程的结束判断,当输入关闭,内外队列都为0时,就可以直接break了。
然后,在思考如何加锁时,首先确定共享对象是什么,哪些代码只能单个线程进入,划定好临界区。然后锁住共享对象或者自己设定的锁即可。
最后,索取资源未有时,便需要wait,(不wait而是采取轮询的话,可能会导致cpu超时,但是听有的同学说巧妙的轮询比wait-notify优越很多)。当资源产生时(或者结束),便进行Notify。
-
层次化设计
这次清晰地体会到了层次化的重要性,将任务按照功能和特性不断细分,形成树状结构,对之后的迭代很有好处。
比如这次的作业当中,让电梯管理自己的运动和队列,因为这都属于电梯自己的运动特性,应该交由电梯自己管理
-
心路历程
写电梯的难度较之表达式可以说是小了不少,因为顾及的细节少了很多。同时策略的选择也很重要,在遇到不懂的地方应该尽快搜索相关资料,而不是闭门造车。
标签:同步,队列,第二,作业,调度,电梯,线程,BUAAOO,单元 来源: https://www.cnblogs.com/zhuxixi/p/14702841.html