OO第二单元博客总结

OO第二单元博客总结

架构展示

根据实现需要,我将程序分为4个线程,分别为Main,InputThread,Scheduler和Elevator线程。

下面逐一进行介绍:

Main:主线程,负责各个进程的创建,初始化,并在其他进程结束后退出。

InputThread:输入处理线程,从输入中读入Request分为PersonRequest和ElevatorRequest。根据读入请求的类型,分别进行不同的操作。

  • PersonRequest:根据换乘的实现需要,我将PersonRequest包装为LiftRequest,并加入全局的waitQueue中。

PS:LiftRequest继承自PersonRequest,添加了的字段为finalFloor,以及三个bool类型,canMoveOnA,canMoveOnB,canMoveOnC。

    private final int finalFloor;
    private boolean canMoveOnA = true;
    private boolean canMoveOnB = true;
    private boolean canMoveOnC = true;

这时,fromfloor和tofloor就表示的是这次乘电梯的起止楼层,finalfloor在读入的时候就已经设置完成,表示的是经过若干次换乘,最终的目标楼层,由此,就可以在每次乘梯时改变from和to,并根据final判断请求是否完成。

  • ElevatorRequest:加电梯指令,也需要根据请求,创建Elevator进程,开启进程。

Scheduler:从waitQueue中读入请求,并根据请求放入每个Elevator维护的一个processingQueue中,供电梯执行。在我的架构中,只要将某个LiftRequest加入到某个processingQueue中,电梯就一定会完成这个请求的一次乘坐(运送到tofloor)。scheduler的具体设计见下文。

Elevator:负责模拟电梯的运行,负责将自己的processingQueue的请求做完,直到processingQueue空为止。

运送我采用的是基本的可稍带算法:

  • 首先,需要在processingQueue中选择一个mainRequest,我的选择标准是距离最长的,我的考虑是mainRequst的运送距离越长,越有可能捎带越多的乘客请求。

  • 将此时的目标就设置为mainRequest的目标楼层,电梯开始运行。

  • 在运行过程中,电梯经过每一层都首先进行是否有乘客到了,并让到了的乘客出去。这里需要根据toFloor和finalFloor来判断是否到达最终楼层,如果还没有到,就把这个Request的fromFloor修改为本层,作为一个新的请求放入waitQueue中,因此waitQueue的来源现在就有inputThread读入和Elevator没有执行完的换乘指令

  • 再进行是否由可稍带乘客的判断。如果processingQueue中的某些请求满足:

    • 起点在本层
    • 方向与目前电梯的方向同向

    那么就在人数允许的情况下,将可稍带请求加入电梯中。重复运行,到processingQueue空为止。

同步块设置与锁选择

不同线程的关系如下

InputThread与Scheduler:

这两个线程采用的是生产者消费者模式:共享的table为waitQueue

因此,按照生产者消费者模式进行编写,将临界区选择为waitQueue的操作。

Scheduler与Elevator:

同样是采用生产者消费者模式:共享的table为对应elevator的processingQueue。因此需要在将临界区选择为processingQueue。

在Scheduler的waitQueue临界区中,对关于processingQueue的操作再次加锁,这样操作是非常危险的,因此,在elevator线程中,不要(也不需要)对waitQueue加锁,否则极大概率死锁

综上,我认为多线程同步块的设置首先是要弄清楚不同线程交互的共享变量,并遵循临界区尽量小的原则,避免写出多个锁嵌套的结构

我的架构就是两个生产者消费者模式,因此可以采用此架构的标准模板,十分方便(还可以保证线程安全)

Provider:
	while(true){
		synchronize(table){
			if(end){
				notifyall;
				return;
			}
			add a request.
			table.notifyall;
		}
	}
Customer:
	while(true){
		synchronize(table){
			if(有请求){
				处理
			}
			else{
			wait;
			}
		}
	}

PS:这里的难点是理清楚各个线程的结束标志和结束操作,各个线程在交互,因此一个线程结束之后的操作也影响到了其他线程的结束

InputThread:

结束标志:读入null

结束操作:将waitQueue的isEnd置为true,并唤起所有waitQueue锁的进程。

Scheduler:

结束标志:由于Scheduler需要处理的请求来源为InputThread和Elevator(换乘)两个线程,因此我们需要判断所有的请求都达到finalFloor。这里再waitQueue中参考信号量机制,引入一个变量,代表此时未到达finalFloor的请求数目,这个数目只能在input读入一个全新请求的时候增加,在Elevator出来的时候如果到达finalFloor的时候减少,当信号量为0,代表所有的请求都最终完成,且waitQueue和processingQueue为空,Scheduler结束。

结束操作:唤起所有电梯进程

Elevator:

结束标志:当信号量为0,代表所有的请求都最终完成,且waitQueue和processingQueue为空,Elevator结束。

结束操作:return

main:所有线程结束完毕,自动退出。

调度器设计

由于我的电梯是一定会把自身请求队列中的请求全部完成的,因此,提高电梯的性能大概率只能看调度器Scheduler分配请求分配得好不好了。

首先先理清关系,调度器是整个结构中交互最复杂的线程,它与Input线程之间是生产者-消费者关系,它是消费者,但是它与Elevator线程之间也是生产者,消费者关系,它此时是生产者。

调度器的行为如下:

  • 外界没有输入请求,wait
  • 拿到请求,将请求根据各个电梯此时的状态选择一个合适的电梯加入到对应的请求队列中。
    • 如果from层此时就有电梯,那么在这群可以直接上的电梯中选择人数最少的。
    • 若没有,在所有电梯中选择同方向的离得最近的。
    • 如果没有同方向的,那么就选择最近的。

但是,有一个问题是这个观察电梯的同时,电梯线程是没有停止地在运行的,电梯线程是不可能wait的,因此我们调度器观察到的电梯状态很可能并不是电梯此时的真实状态,因此调度器可能根据这个错误的信息将请求放入错误的请求队列,而我的实现是放入请求队列后就一定要执行结束,因此没有任何的补救措施,这是多线程需要理解的一个点。

但是荣老师上课也提到了这一点,他指出,我们并不需要一定获得电梯最真实的状态,在调度程序不长的情况下,我们只需要保证大部分请求放入正确的请求队列就可以了,偶尔一两个请求正好放错也没有关系。

但是,由于我的调度策略太长了,找到最小值必须要遍历,而且最差的情况还要分情况遍历三遍,这就导致我的调度器“看一眼”的时间就很长,看到的早就不是电梯真实的状态了,因此,我发现我辛辛苦苦写了半天的调度策略可能在某些数据下还没有随机分来的快(不是这种”看一下“的方法不好,而是我自己写呲了^^)

但是,上课听了一下同学们的分享,还是收获了很多:

我们可以设置一个惩罚的函数,这个函数根据决定分配调度的各种因素的加权和来判断最优解:

  • 电梯类型,这个权值应当是非常高的(C类比A类快3倍)
  • 是否同向
  • 电梯离出发点的距离

这些因素综合考虑设置好参数后,只需要一个函数的计算就可以得到一个预期值,再根据这些值选择一个最优解即可,这样应该比我那个多次遍历要好很多。

UML协作图与代码分析

类图如下:

各个线程之间的协作关系如下:

代码规模如下:

Task1:

method CogC ev(G) iv(G) v(G)
Elevator.abs(int) 2.0 2.0 1.0 2.0
Elevator.aslDispatch() 40.0 7.0 11.0 13.0
Elevator.changeDir() 2.0 1.0 3.0 3.0
Elevator.closeTheDoor() 1.0 1.0 2.0 2.0
Elevator.Elevator(ProcessingQueue,WaitQueue,int) 0.0 1.0 1.0 1.0
Elevator.getAllRequests() 0.0 1.0 1.0 1.0
Elevator.getByTheWay() 5.0 1.0 4.0 5.0
Elevator.getCurrentFloor() 0.0 1.0 1.0 1.0
Elevator.getDes() 0.0 1.0 1.0 1.0
Elevator.getDesByTheWay() 0.0 1.0 1.0 1.0
Elevator.getDir() 0.0 1.0 1.0 1.0
Elevator.getDir(int) 3.0 3.0 1.0 3.0
Elevator.getMainRequest() 3.0 1.0 2.0 3.0
Elevator.getNumPeople() 0.0 1.0 1.0 1.0
Elevator.getProcessingQueue() 0.0 1.0 1.0 1.0
Elevator.getType() 0.0 1.0 1.0 1.0
Elevator.getWaitQueue() 0.0 1.0 1.0 1.0
Elevator.isIn() 0.0 1.0 1.0 1.0
Elevator.moveOneFloor(int) 3.0 1.0 2.0 4.0
Elevator.needtoChangeDir() 12.0 6.0 4.0 6.0
Elevator.needToOpen() 4.0 3.0 5.0 7.0
Elevator.openAndClose() 8.0 1.0 5.0 6.0
Elevator.openAndCloseMain() 8.0 1.0 5.0 6.0
Elevator.openTheDoor() 1.0 1.0 2.0 2.0
Elevator.passengerIn(PersonRequest) 0.0 1.0 1.0 1.0
Elevator.passengerOut(PersonRequest) 0.0 1.0 1.0 1.0
Elevator.run() 26.0 5.0 11.0 13.0
InputThread.InputThread(WaitQueue,ArrayList) 0.0 1.0 1.0 1.0
InputThread.run() 4.0 3.0 3.0 3.0
Main.main(String[]) 6.0 1.0 4.0 5.0
ProcessingQueue.addRequest(PersonRequest) 0.0 1.0 1.0 1.0
ProcessingQueue.getId() 0.0 1.0 1.0 1.0
ProcessingQueue.getRequests() 0.0 1.0 1.0 1.0
ProcessingQueue.isEmpty() 0.0 1.0 1.0 1.0
ProcessingQueue.ProcessingQueue(int) 0.0 1.0 1.0 1.0
Scheduler.run() 39.0 3.0 13.0 13.0
Scheduler.Scheduler(WaitQueue,ArrayList,int,Elevator) 0.0 1.0 1.0 1.0
WaitQueue.addRequest(PersonRequest) 0.0 1.0 1.0 1.0
WaitQueue.clearQueue() 0.0 1.0 1.0 1.0
WaitQueue.close() 0.0 1.0 1.0 1.0
WaitQueue.getRequests() 0.0 1.0 1.0 1.0
WaitQueue.isEnd() 0.0 1.0 1.0 1.0
WaitQueue.noWaiting() 0.0 1.0 1.0 1.0
WaitQueue.WaitQueue() 0.0 1.0 1.0 1.0
Total 167.0 68.0 105.0 123.0
Average 3.7954545454545454 1.5454545454545454 2.3863636363636362 2.7954545454545454

Task2:

method CogC ev(G) iv(G) v(G)
Elevator.abs(int) 2.0 2.0 1.0 2.0
Elevator.aslDispatch() 40.0 7.0 12.0 14.0
Elevator.changeDir() 2.0 1.0 3.0 3.0
Elevator.closeTheDoor() 1.0 1.0 2.0 2.0
Elevator.Elevator(ProcessingQueue,WaitQueue,int,int) 0.0 1.0 1.0 1.0
Elevator.getByTheWay() 5.0 1.0 4.0 5.0
Elevator.getCurrentFloor() 0.0 1.0 1.0 1.0
Elevator.getDes() 0.0 1.0 1.0 1.0
Elevator.getDir() 0.0 1.0 1.0 1.0
Elevator.getDir(int) 3.0 3.0 1.0 3.0
Elevator.getMainRequest() 3.0 1.0 2.0 3.0
Elevator.getProcessingQueue() 0.0 1.0 1.0 1.0
Elevator.getRealNum() 7.0 3.0 2.0 5.0
Elevator.getTotalNum() 4.0 3.0 3.0 3.0
Elevator.moveOneFloor(int) 3.0 1.0 2.0 4.0
Elevator.needtoChangeDir() 12.0 6.0 4.0 6.0
Elevator.needToOpen() 4.0 3.0 5.0 7.0
Elevator.openAndClose() 8.0 1.0 5.0 6.0
Elevator.openAndCloseMain() 9.0 1.0 6.0 7.0
Elevator.openTheDoor() 1.0 1.0 2.0 2.0
Elevator.passengerIn(PersonRequest) 0.0 1.0 1.0 1.0
Elevator.passengerOut(PersonRequest) 4.0 1.0 3.0 4.0
Elevator.run() 26.0 5.0 11.0 13.0
Elevator.setType(int) 0.0 1.0 1.0 1.0
InputThread.InputThread(WaitQueue,ArrayList,ArrayList,ArrayList) 0.0 1.0 1.0 1.0
InputThread.run() 8.0 3.0 4.0 4.0
InputThread.setElevators(ArrayList) 0.0 1.0 1.0 1.0
InputThread.setType(int) 0.0 1.0 1.0 1.0
Main.main(String[]) 7.0 1.0 5.0 6.0
ProcessingQueue.addRequest(PersonRequest) 0.0 1.0 1.0 1.0
ProcessingQueue.getId() 0.0 1.0 1.0 1.0
ProcessingQueue.getRequests() 0.0 1.0 1.0 1.0
ProcessingQueue.isEmpty() 0.0 1.0 1.0 1.0
ProcessingQueue.ProcessingQueue(int) 0.0 1.0 1.0 1.0
Scheduler.abs(int) 2.0 2.0 1.0 2.0
Scheduler.getDir(int,int) 3.0 3.0 1.0 3.0
Scheduler.getElevatorIndex(PersonRequest) 39.0 4.0 20.0 21.0
Scheduler.run() 39.0 3.0 13.0 13.0
Scheduler.Scheduler(WaitQueue,ArrayList,int,ArrayList) 0.0 1.0 1.0 1.0
WaitQueue.addRequest(PersonRequest) 0.0 1.0 1.0 1.0
WaitQueue.clearQueue() 0.0 1.0 1.0 1.0
WaitQueue.close() 0.0 1.0 1.0 1.0
WaitQueue.getRequests() 0.0 1.0 1.0 1.0
WaitQueue.isEnd() 0.0 1.0 1.0 1.0
WaitQueue.noWaiting() 0.0 1.0 1.0 1.0
WaitQueue.WaitQueue() 0.0 1.0 1.0 1.0
Total 232.0 80.0 135.0 161.0
Average 5.043478260869565 1.7391304347826086 2.9347826086956523 3.5

Task3:

Elevator.abs(int) 2.0 2.0 1.0 2.0
Elevator.abs(int) 2.0 2.0 1.0 2.0
Elevator.aslDispatch() 40.0 7.0 12.0 14.0
Elevator.canOpen() 4.0 4.0 1.0 7.0
Elevator.changeDir() 2.0 1.0 3.0 3.0
Elevator.closeTheDoor() 1.0 1.0 2.0 2.0
Elevator.Elevator(ProcessingQueue,WaitQueue,int,int,int) 1.0 1.0 1.0 4.0
Elevator.getByTheWay() 5.0 1.0 4.0 5.0
Elevator.getCurrentFloor() 0.0 1.0 1.0 1.0
Elevator.getDes() 0.0 1.0 1.0 1.0
Elevator.getDir() 0.0 1.0 1.0 1.0
Elevator.getDir(int) 3.0 3.0 1.0 3.0
Elevator.getEleMaxNum() 0.0 1.0 1.0 1.0
Elevator.getEleType() 0.0 1.0 1.0 1.0
Elevator.getMainRequest() 3.0 1.0 2.0 3.0
Elevator.getProcessingQueue() 0.0 1.0 1.0 1.0
Elevator.getRealNum() 7.0 3.0 2.0 5.0
Elevator.getTimeperFloor() 0.0 1.0 1.0 1.0
Elevator.getTotalNum() 4.0 3.0 3.0 3.0
Elevator.moveOneFloor(int) 4.0 1.0 2.0 7.0
Elevator.needtoChangeDir() 12.0 6.0 4.0 6.0
Elevator.needToOpen() 8.0 4.0 6.0 8.0
Elevator.openAndClose() 8.0 1.0 5.0 6.0
Elevator.openAndCloseMain() 9.0 1.0 6.0 7.0
Elevator.openTheDoor() 1.0 1.0 2.0 2.0
Elevator.passengerIn(LiftRequest) 0.0 1.0 1.0 1.0
Elevator.passengerOut(LiftRequest) 8.0 1.0 4.0 8.0
Elevator.run() 26.0 5.0 13.0 15.0
Elevator.setType(int) 0.0 1.0 1.0 1.0
InputThread.InputThread(WaitQueue,ArrayList,ArrayList,ArrayList) 0.0 1.0 1.0 1.0
InputThread.run() 12.0 3.0 4.0 7.0
InputThread.setElevators(ArrayList) 0.0 1.0 1.0 1.0
InputThread.setType(int) 0.0 1.0 1.0 1.0
LiftRequest.getFinalFloor() 0.0 1.0 1.0 1.0
LiftRequest.isCanMoveOnA() 0.0 1.0 1.0 1.0
LiftRequest.isCanMoveOnB() 0.0 1.0 1.0 1.0
LiftRequest.isCanMoveOnC() 0.0 1.0 1.0 1.0
LiftRequest.LiftRequest(int,int,int,int) 0.0 1.0 1.0 1.0
LiftRequest.LiftRequest(int,int,int,int,boolean,boolean,boolean) 0.0 1.0 1.0 1.0
LiftRequest.LiftRequest(PersonRequest,int) 0.0 1.0 1.0 1.0
LiftRequest.toString() 0.0 1.0 1.0 1.0
Main.main(String[]) 7.0 1.0 5.0 6.0
ProcessingQueue.addRequest(LiftRequest) 0.0 1.0 1.0 1.0
ProcessingQueue.getId() 0.0 1.0 1.0 1.0
ProcessingQueue.getRequests() 0.0 1.0 1.0 1.0
ProcessingQueue.isEmpty() 0.0 1.0 1.0 1.0
ProcessingQueue.ProcessingQueue(int) 0.0 1.0 1.0 1.0
Scheduler.abs(int) 2.0 2.0 1.0 2.0
Scheduler.calToFloor(LiftRequest,int) 14.0 7.0 10.0 11.0
Scheduler.canMoveOn(Elevator,LiftRequest) 3.0 4.0 6.0 6.0
Scheduler.canOpen(int,int) 4.0 4.0 1.0 7.0
Scheduler.getDir(int,int) 3.0 3.0 1.0 3.0
Scheduler.getElevatorIndex(LiftRequest) 43.0 4.0 30.0 31.0
Scheduler.run() 45.0 3.0 17.0 17.0
Scheduler.Scheduler(WaitQueue,ArrayList,int,ArrayList) 0.0 1.0 1.0 1.0
WaitQueue.addNumPersonRequest() 0.0 1.0 1.0 1.0
WaitQueue.addRequest(LiftRequest) 0.0 1.0 1.0 1.0
WaitQueue.clearQueue() 0.0 1.0 1.0 1.0
WaitQueue.close() 0.0 1.0 1.0 1.0
WaitQueue.getNumPersonRequest() 0.0 1.0 1.0 1.0
WaitQueue.getRequests() 0.0 1.0 1.0 1.0
WaitQueue.isEnd() 0.0 1.0 1.0 1.0
WaitQueue.noRequest() 0.0 1.0 1.0 1.0
WaitQueue.noWaiting() 0.0 1.0 1.0 1.0
WaitQueue.subNumPersonRequest() 0.0 1.0 1.0 1.0
WaitQueue.WaitQueue() 0.0 1.0 1.0 1.0
Total 281.0 115.0 186.0 237.0
Average 4.323076923076923 1.7692307692307692 2.8615384615384616 3.646153846153846

由此可以看出,在Scheduler和Elavtor线程的run方法中,没有很好的做到分层次处理,导致这两个线程中的run方法复杂度过高,而且随着需求的扩展,复杂度会越来越高,这也直接导致了第三次作业风格分很难修改,因为一直超行数。

架构功能上的扩展性应该尚可,后两次作业几乎没有做什么修改。

自己程序Bug分析

Task1和Task3均未出现bug,也没有被hack。

Task2有一个bug是因为两个线程交叉使用锁导致死锁,这个错误本来在课下是发现了的,结果修改的时候只改了两个模式,忘了还有一个模式了,就忘了改。。还是太粗心了。

他人程序的Bug分析

三次作业没有Hack出别人的Bug

心得体会

线程安全

通过本次作业,了解了Java多线程的写法加深了对多线程并发执行的理解。

线程安全是在第一次作业最困扰我的一个问题,一路走来,对线程安全这个问题也有了自己的认识,给出一些我自己的建议:

  • 首先,在初次接触线程的时候,可以根据自己需要使用的模式(例如在电梯作业中广泛运用的生产者消费者模式)在网上查找一些现成的模板,在多线程不熟悉的情况下可以先照着模板写程序,避免苦思冥想陷入死胡同,重点先放在功能的实现上,线程安全的问题放在debug的时候再修改,毕竟如果不先实现功能的话debug都无从谈起。
  • 其次,在debug的时候使用JProfiler来辅助找到线程的问题。
  • 要理解多线程在退出之前都是一个while(true)的死循环,可以辅助一些OS理论课的进程同步互斥理论,这样配合起来理解会更加深刻。
  • 自己写清楚不同线程之间的共享变量,临界区极可能缩小。
  • 尽量不要
sychronized(A){
	sychronized(B){
	
	}
}

但是这个有时候没有办法。

  • 千万不要交叉加锁(说了好几遍了),基本上是必死锁的,(除非你也想刺激一把^^),肯定有办法修改的,如果实在不会了,可以学习我舍友,少量轮询大部分时候也是不会超时的。

设计模式

善于使用OOP的基本模型

  • 生产者-消费者模式:在输入和调度器,调度器与电梯之间都可以使用,是作业中最广泛使用的模式
  • 单例模式:全局获取电梯状态的elevators数组,elevator,waitQueue,每一个LiftRequest都是唯一的,可以使用单例模式封装创建,并方便全局访问,否则必须在各个线程中都加入对应的字段,构造时传入,十分麻烦,单例模式可以有效减少代码量。
  • 状态模式:电梯其实可以看作一个有限状态机,因此按照有限状态机来进行建构电梯,这个我没有使用,当时没想到,但是感觉应该是很好的办法。

层次化设计

做设计首先要构思好若干完成任务的线程和类,类和类之间要层次分明,高内聚低耦合。

例如 inputThread:只负责向waitQueue中添加请求。

而Scheduler:就可以直接拿到waitQueue的请求,而不需要关心inputThread中的请求读入判断等操作。再把请求用某种算法放入对应的电梯processingQueue中。

Elevator:只需要关注拿到了processingQueue中的若干请求,如何把这些请求尽可能快的运送到目标楼层即可,也不需要管请求的分配方法,分配到自己的就一定要执行完。

这样三层的分层处理将具体的算法策略都隐藏在了线程的内部,只对外暴露本层实现的结果即可。

这一单元总体来说还是比较轻松的,希望下个单元能细心一些,再接再厉。

posted @ 2021-04-24 15:37  yyfyyfyyf  阅读(68)  评论(0)    收藏  举报