oo第二单元总结

第二单元总结


前言

电梯月终于快要结束了,现在写博客的心情是十分轻松的。轻松之处在于两方面吧。首先,这一单元我完成得比上一单元顺利不少,虽然也有些小问题,但都无伤大雅。其次就是迎来了短暂的休息时间。

UML协作图

请添加图片描述

一、第一次作业

1、作业分析

第一次作业主要就是实现一个多线程的电梯,并且每名乘客都已经指定了所要乘坐的电梯,所以只需要实现电梯纵向上的运行策略即可。

2、UML类图

请添加图片描述

3、架构分析

在本次作业中,我采用输入线程加电梯线程的架构,输入线程和电梯线程共享等待队列。
对于锁,我选择了最简单的同步锁synchronized,同时我没有使用方法锁,即将锁作用于方法,而是用对象锁,需要同步的地方锁住相应的对象这样实现起来我感觉比较清晰,同时也可能是因为我当时对于方法锁理解不够透彻所致,其实二者本质上是一样的,锁都是作用于对象,对于方法锁,锁住的就是当前对象,而同步代码块就是所有同步方法中的相应的代码,不只局限于当前的方法,如果是对象锁,锁住的就是synchronized大括号之间的代码。
刚开始的时候我想过是否应该加一个调度器线程,我感觉既然输入已经规定好了每位乘客所要进入的电梯,所以在输入的时候直接进行分配即可,所以,我就把调度器线程给去掉了,在后面几次作业中我也没有实现调度器线程,至于原因就留到后面再说吧。
然后我的电梯运行策略并不是大多数人使用的look策略:这也为我第六次作业的小重构埋下了伏笔。
我的电梯运行策略类似于课程组提供的ALS策略,但是不同的是寻找主请求的时候是寻找出发楼层与当前电梯所在楼层距离最远的那个请求作为主请求,同时在电梯运行时每个漏测都判断是否要稍带,如果捎带了,就判断是否要更新toFloor
虽然这个策略看起来比较简单,但是写起来相较于look策略还是麻烦了些,而且性能方面也不如look策略。当时我也不知道怎么的脑子一热就写了这样一个策略。还有一点就是我没有对电梯进行相应的优化,比如实现量子电梯,还有是在同一层多次开关门提高运行效率。

4、调度策略

在第一次作业中由于为每位乘客指定了相应的电梯,所以不需要什么调度策略。

5、实现细节

在本次作业中主要关注的细节就是锁的应用,刚开始的时候由于对锁的知识理解不够,有些该加锁的地方没有加锁,不该加锁的地方加了锁,导致我的代码经常出现死锁等问题,所以我首先把锁的知识吃透,以及相应的等待和唤醒的时间理清,之后我的思路就变得比较清晰,到最后,输入线程和每个电梯线程之间所要共享的资源只有电梯中的等待队列,其余的地方则不需要加锁。
对于wait()notifyAll()这两个方法主要是实现线程之间的交互,使得相应的线程同步进行,即必须输入线程向等待队列中添加乘客之后电梯线程才可以运行。在确定好共享资源之后,wait()就是在电梯线程在等待队列为空且电梯中的人数为空时将该线程暂停,以免其浪费cpu资源,notifyAll()则是在输入线程向相应的电梯输送请求之后唤醒等在该电梯的等待队列上的电梯线程。在我看来,其实使用notify()效果时一模一样的
,因为只有每个电梯的等待队列上只可能有一个线程在等待,所以notify()notifyAll()的可唤醒的线程数就是小于等于一,在本次作业中,我使用了一个wait()和两个notifyAll()便实现了相应的同步操作。
还有就是电梯线程结束的时间,在输入线程输入结束的时候,电梯线程可能还没有将所有的请求处理完毕,所以我的处理方式是向电梯类发送一个输入结束的信号,同时还要判断该电梯的等待队列是否为空和相应的电梯内部乘客是否为0,如果不满足则不可退出。
还有一个小细节就是对于每个电梯的请求队列,我并没有用Person类作为等待队列中的元素,因为新建一个Person类其中的属性无非就是fromFloortoFloorpersonId三个,所以我用三层Arraylist来存储每个请求,第一层代表请求的fromFloor,然后我在把每一层的请求设置了一个类RequestsEveryFloor,然后其中有个两层的Arraylist,第一层是toFloor,第二层则记录了去往同一层的乘客的ID

6、bug分析

在本次作业中,课下主要是死锁问题。强测和互测中并没有出现bug,但是这次作业的性能分并没特别理想,主要就是我的电梯纵向运行策略不是特别好,其次是电梯运行时的细节没有优化好。

7、互测

互测中我主要的hack策略是在同一时刻输入大量数据,借助这一思想,我成功地hack到了一个人。由于这次作业限制了每个电梯最多输入的请求数,所以超时是不太可能的,所以这次互测没有根据这一策略刀到人。

二、第二次作业

1、作业分析

第二次作业相对于第一次作业来说的话改动还是比较大的,首先就是不在给乘客致电给相应的乘坐的电梯,这就使得我们自己需要添加相应的调度策略来让六个电梯协同处理所有的请求;其次就是添加了电梯重置请求,同时要求电梯在移动时必须要输出Receive,这就变相地使得之前大为流行的自由竞争策略化成了泡影,但是听说通过相应的改动还是可以实现一个退化版的自由竞争的

2、UML类图

请添加图片描述

3、架构分析

在本次作业中,我沿用了上一次作业的架构,但是对电梯类我进行了小重写,主要就是对电梯运行的策略进行了修改,将电梯运行策略换成了look策略,即:

当前方向上有人时继续向前运行,如果前面没人了并且反方向上有人等待,电梯运行方向相反,如果反方向上没有人的话电梯停下,等待输入线程分配请求。

这个策略实现起来是比较容易的,性能也不低。
由于需要对输入请求进行调度,大多数人会选择开一个调度器线程进行资源的分配,但是,我思考之后还是继续沿用第一次作业输入线程和电梯线程的架构,原因主要如下:
首先,如果新增一个调度器线程,输入线程向总请求队列中写的时候相当于是一个单线程操作,因为输入线程和调度器线程互斥,二者不能同时操作总请求队列,其次,当调度器线程获得了总请求队列的锁之后,进行相应的请求分配,在这两步操作中,由于互斥关系的存在,我感觉相当于是单线程的操作,增加的调度器线程可能也就是在调度策略上会使得两个线程并发执行,但是这个时间取决于调度策略的复杂度,我的调度策略使用的是电梯参数所构造的函数,复杂度并不高,并不会耗费很多时间,综合以上原因,我没有采取新增调度器线程。而是在输入线程输入的时候就执行相应的调度策略将相应的请求分到相应的电梯。其实还有一个原因就是我比较懒。

4、调度策略

调度策略在本次作业是最重要的一环,如果调度策略设计的好的话,性能分会十分高。刚开始我也想找一个完美的调度策略,但是在我绞尽脑汁思考之后,我发现任何调度策略都不会没有任何瑕疵,也就是必然存在另一个调度策略比起性能好,所以我考虑到前几届学长所推崇的自由竞争策略,我想简单的策略可能是比较好的,我想平均分配策略或许是一个比较好的策略。所以我在开始的时候选择了平均分配策略。
在临近作业提交的截止时间的时候,我偶然看到某学长的博客,看到它采用了一种以电梯相应的参数所构造的函数的方法,我想这个策略应该会比平均分配的性能好一点,而且实现起来也比较简单,所以,我最后采用了这种类似于打分的模式,调度函数如下:

人和电梯同方向
{
	人在电梯前面或同层:Move * abs ( NowFloor - NowFromFloor )
	人在电梯后面:
    {
    	电梯向上:ans = max(向上的人的ToFloor, 向下的人的FromFloor)
 		电梯向下:ans = min(向下的人的ToFloor, 向上的人的FromFloor)
 		MoveTime * ( abs( ans - NowFloor ) + abs( ans - NowFromFloor ) )
	}

}

人和电梯反方向
{
 	电梯向上:ans = max(向上的人的ToFloor, 向下的人的FromFloor)
 	电梯向下:ans = min(向下的人的ToFloor, 向上的人的FromFloor)
 	MoveTime * ( abs( ans - NowFloor ) + abs( ans - NowFromFloor))
}

说实话,在写完这个策略之后,我感觉性能分可能会在一个比较均衡的水平,不会太高也不会太低,但是我确实低估了该策略的性能,看来许久我也没能想明白该策略的优秀之处在哪,可能是我太菜了吧
该策略看上去像是平均分配的改进版,其运行时间上并没有太快,可能就是相应的耗电量以及送人的时间的均值较小吧。
在强测出结果之前,我甚至想过重写一下调度策略,采用大多数人推崇的影子电梯,以提高一下我的性能分,甚至在周一晚上之前,影子电梯我完成80%左右,但是在强测结果出来之后,完全打消了我重构的念想,我还是踏踏实实用这种调度策略吧,事实证明,或许简单的真的是好的。

5、实现细节

在本次作业中如果说最烧脑的是调度策略的设计,那么细节最多的就是reset请求的处理。
首先我在电梯类中添加了一个是否reset的标志位,当reset请求需要处理时,首先需要进行一次notifyAll(),使得相应的电梯如果在等待就将其唤醒,将该标志位改为true,然后由于我在每次while循环中只进行一个动作,即要么开门上下人,要么进行电梯的move,所以使得在两次move之间电梯可以处理该reset请求,从而不会超出指导书的限制,其次,输入线程需要相应地wait(),以等待电梯线程做出相应的等待。在电梯线程识别到reset信号之后,输入线程和相应的电梯线程都在处理该reset,在我的代码中,输入线程负责相应的电梯中的和等待队列中的乘客的再分配工作,所以该工作需要电梯线程将电梯中的人全部移入等待队列中之后进行,在这里,需要进程之间的同步,所以首先输入线程wait(),然后在电梯线程执行完相应的任务之后再对该线程进行notifyAll(),这样保证逻辑上不会出现问题。然后分配乘客的任务和从输入中读取没什么区别。

// Elevator.java
 public void reset() throws InterruptedException {
        synchronized (inRequests) {
            //将电梯中的人全部移入等待队列
            inRequests.notifyAll();
        }
        do something
//InputThread.java
public void reset(ResetRequest resetRequest) throws InterruptedException {
        int elevatorID = resetRequest.getElevatorId() - 1;
        synchronized (requestQueue.get(elevatorID)) { //获取相应电梯的等待队列的锁
            Elevator elevator = elevators.get(elevatorID);
            elevator.setNowReset(resetRequest);
            elevator.setResetStatus();
            requestQueue.get(elevatorID).notifyAll();
            requestQueue.get(elevatorID).wait();
            elevator.setCapacity(resetRequest.getCapacity());
            elevator.setMoveTime(resetRequest.getSpeed());
            for (int i = 0; i < FLOOR_NUM; i++) {
                //等待队列中的乘客再分配
            }
        }
    }

6、bug分析

在本次作业中,课下写得算是比较顺利,强测没出现错误,但是互测中被hack了很多次,但是这些hack都是一个bug,主要原因就在于我在对请求分配相应的电梯时,如果该电梯是reset的电梯我直接给跳过了,所以如果在同一时刻接受了五个电梯的reset同时还有大量的请求,这些请求就会全部分给剩余的那个电梯,从而使得超出程序运行的时间。
这个bug也比较好改,只需要在每个电梯中设置buffer队列,同时在分配时把reset的电梯考虑在内,分配时,如果电梯在reset的状态,将请求塞入buffer中,当reset结束的时候,再把里面的乘客移入等待队列,同时输出receive

7、互测

在互测之中,我也用过别人hack我的数据对其他人进行hack,这个数据属于极端情况,还有通过跑评测机发现其他人的程序在某些情况下会出现错误。

三、第三次作业

1、作业分析

本次增加的主要内容就是双轿厢电梯,实现方式就是通过第二类reset。难点就是协调两个轿厢在转运楼层不会相撞。其他和普通电梯没什么区别。

2、UML类图

请添加图片描述

3、架构分析

本次作业的架构和第二次作业的改动很小,主要就是请求分配时需要考虑到普通电梯和双轿厢电梯的运行楼层不同。对于双轿厢电梯,我没有采用继承的方法,而是先建了一个普通类,因为我感觉原来的电梯的代码移到双轿厢上来时都需要改。

4、调度策略

调度策略仍沿用上面的得分函数的模式,只不过需要考虑到双轿厢电梯的运行楼层的不同,所以要改变其中相应的参数。

5、实现细节

本次作业的双轿厢电梯可以看作是两个普通电梯相结合,只不过和普通电梯不同的是其可达楼层,以及转运楼层的避撞问题。可达楼层的问题只需要设置两个变量minFloormaxFloor即可,对于避撞问题,我对A、B电梯设置了一个共享类Transfer,用来表示转运楼层,当一个电梯到达转运楼层时,该线程获得该实例对象的锁,当它离开转运楼层时,释放锁,这里如果获得锁的电梯我强制让其离开转运楼层,也就是如果没有乘客的话也会直接离开转运楼层,所以该锁一定会被释放。

6、bug分析

本次作业课下出现的主要问题就是转运楼层的锁问题,开始我用的是请求队列的锁,出现了点小bug,还有就是我在Dc电梯中打破了一次循环只完成一个动作的原则,这里出现了bug。解决了以上的bug之后,在强测和互测中没有出现bug。

7、互测

本次互测中,我仍使用上次互测的思想,构造极端数据和跑评测机,极端数据就是构造容易超时的数据。跑评测机本次没有收获。

四、心得体会

第二单元相对于第一单元来说是没有那么复杂,第二单元重点就是多线程之间的互斥和同步,即线程安全,在我的代码中只涉及输入线程和电梯线程的互斥和同步,互斥体现在只有一个线程可以使用共享资源,同步体现在必须有输入电梯线程才可以运行,还有就是两个电梯之间的互斥,这体现在双轿厢电梯,只有一个电梯可以进入转运楼层。
解决了线程之间的同步和互斥问题,该单元剩下的就是设计好自己的两个策略:新来的请求分配给哪个电梯?电梯纵向运行策略是什么?解决好上面两方面的问题,该单元的设计思路也就比较清晰了。

  • 27
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值