301
社区成员
发帖
与我相关
我的任务
分享

7个类
主要负责启动Input,Schedule,Elevator线程
输入线程,读取请求输入,并将其加入到总请求队列waitrequest中
调度器线程,从waitrequest中获取请求并分配到各个电梯中
电梯线程,根据获得的decision执行每一步操作,完成运送乘客任务
根据电梯当前状态判断电梯下一步应该进行的操作
请求类,多线程间的共享对象,其中的方法需要加上锁synchronized来保证线程安全
乘客类,储存请求人员的信息
在第一次作业中,我设置的线程类有Input,Schedule和Elevator三个类,这三个类的共享对象其实都是请求队列。因此,我将Request类里的方法进行了同步设置,都加上了锁来进行管理。
这样加锁之后,Input类和Schedule类对总请求队列waitrequest的管理是同步的,Schedule类和每个Elevator的局部队列也是同步的,从而保证了线程安全的问题
在第一次作业中,调度器发挥的作用其实并不大,因为每个乘客都指定了需要搭乘的电梯,Schedule类的作用就只用从总队列中取出乘客,再识别出其需要搭乘的电梯,最后加入到相关电梯的乘客队列中即可。
在这个过程中,Schedule线程与Input线程两者通过waitrequest总请求队列联系,Input线程充当生产者,Schedule线程充当消费者,当waitrequest队列为空时,Schedule线程等待,Input线程向waitrequest队列加入时,将Schedule线程唤醒。当Input线程结束时,会将waitrequest队列的结束标志置1,从而让Schedule线程读取到后进入结束阶段。
同时,Schedule线程与每个电梯线程通过电梯各自的request队列相联系,Schedule线程充当生产者,Elevator线程充当消费者,当request队列为空时,Elevator线程等待,Schedule线程向request队列加入时,将Elevator线程唤醒。当Schedule线程结束时,会将request队列的结束标志置1,从而让Elevator线程读取到后进入结束阶段。
第一次作业在最终的呈现中是没有出现Bug,但是在编写过程中,我仍遇到了一些问题。
第一个是关于多线程的理解问题,第一次遇到多线程,一开始还有些懵,但好在也是通过查找资料,学习等摸到了一些皮毛。
另外的一个是关于线程结束的问题。线程的开始十分简单,但是对线程结束,需要考虑什么时候结束,线程怎么判断应该结束的问题。第一次作业的任务中可以发现,Schedule类结束的标志是请求队列为空即可,Elevator类则是请求与电梯中的人均为空即可结束。对于线程判断结束,我是在Request类中设置了结束标志,让每个线程通过判断结束标志是否置1来帮助确定结束。


存储Reset指令的相关信息,保障电梯Reset过程的进行
加入对读入指令的判断,分别加入waitrequest总队列的不同类型的分队列
加入关于Reset指令的判断与处理,将Reset之后产生的换乘请求加入回waitrequest总请求队列中
加入关于Reset指令的判断与处理,能够改变电梯属性并完成Reset操作
在第一次作业的person类请求队列的基础上,加入reset类的请求队列,使得能够存储并输出reset请求
在第二次作业中,我设置的线程类仍是Input,Schedule和Elevator三个类,这三个类的共享对象仍然都是请求队列。但是在第二次作业中新加入了对电梯的Reset请求,对于Reset请求的处理,除了原来的共享对象request队列外,电梯内的乘客people也需要同步控制,从而避免在Reset过程中出现乘客遗漏的情况。因此在第二次作业中在进行电梯Reset的处理时,我加上了关于电梯内部乘客people的锁,使在将电梯未处理请求与电梯内乘客的换乘请求统一加回waitrequest总请求队列的时候,不会出现同步问题,从而保证了线程安全。
在第二次作业中,取消了第一次作业中乘客指定电梯运送的条件,将由调度器决定将乘客请求分给某一步电梯进行运送。因此Schedule类就需要在第一次作业的基础上进行扩充,需要根据每名乘客的相关信息(如起始楼层,目标楼层),并结合当前每部电梯的情况(如当前所在楼层,运行方向,运行速度等等)进行分配请求的判断。
在这个过程中,Schedule线程与Input线程两者依旧通过waitrequest总请求队列联系,Input线程充当生产者,Schedule线程充当消费者,当waitrequest队列为空时,Schedule线程等待,Input线程向waitrequest队列加入时,将Schedule线程唤醒。当Input线程结束时,会将waitrequest队列的结束标志置1,但是Schedule类的结束在这次中不仅仅要看waitrequest队列是否为空,还需要考虑电梯Reset所可能产生的新的请求。
Schedule线程与每个电梯线程通过局部队列request联系。在第一次作业的基础上,加入了关于reset请求的处理。如果Schedule类要想Elevator类分配reset请求,则先将电梯中的reset标志位置1,再将reset请求加入电梯队列request中,让电梯执行reset操作,避免出现无法及时响应reset请求的情况。在电梯reset操作开始后,Schedule类还需要将电梯由于换乘产生的新的乘客请求加回waitrequest总请求队列。
可能使用的调度策略有:
random分配
将乘客随机分配给各个电梯
均匀分配
将乘客均匀分配给各个电梯(模6大法)
分配给接到乘客请求所需时间最少的电梯
仅考虑接到乘客时间的最短“路径”方法
调参大法
综合考虑各个因素(电梯接到乘客时间,电梯现请求数量,电梯人数,预计完成时间等等),并设置相关参数,即各个因素的权重,最后综合考虑分配。
影子电梯
模拟电梯的运行,分配给花时间最少的电梯。即在输入线程获得一个请求时,深克隆电梯类进行模拟从而将请求分配各所花费时间最少的电梯;
在这次作业中未能实现影子电梯,没有能够体会到号称最强的调度策略。在实际体验中发现,均匀分配的总性能并不会太差,比random的性能更好。另外编写了“接待最短时间”与“调参大法”的策略代码,后来发现还并没有比均匀分配效果好多少,甚至会出现更差的情况。(也许该换回均匀分配 > _ <)
这次作业中出现了一个bug,是在程序输出“reset accept”和电梯输出“reset begin”之间输出的“arrive”指令数量超过了3条。导致这个Bug的原因是在程序接受到reset指令跟电梯进入reset状态之间的时间间隔太大,导致电梯在这段时间内做出了过多的移动操作。我在一开始电梯进入reset操作,是由decision判断电梯的resetrequest队列不为空就开始进行。在reset操作的一开始电梯自身将reset标志位置1,此时才算电梯进入reset状态。在接受到这个reset标志位置1后,Schedule类将不在为该电梯分配请求。因此,我将电梯进入reset状态的时间前移,在Schedule类为相关电梯分配reset请求时,就将改电梯的reset标志位置1,使电梯进入reset状态,电梯进入reset操作也变为判断reset标志位,使得电梯在收到reset请求后能够尽快进行reset操作,从而在程序输出“reset accept”和电梯输出“reset begin”之间电梯操作减少。


改为两层调度器模式,此为内层调度器,为所管理的电梯分配请求,完成从单轿厢到双轿厢的转换(结束单轿厢进程,开始双轿厢进程),完成双轿厢电梯的换乘
双轿厢电梯,与单轿厢电梯类似,改变了运行楼层,新增了换乘方法
标志类,用来管理双轿厢电梯的换乘操作
用以存储双轿厢电梯重置请求的信息
改为两层调度器模式,此为外层调度器,向内层调度器分配请求
单轿厢电梯,即先前作业电梯
存储单轿厢电梯换乘请求,即先前作业请求
加入关于双轿厢电梯的运行判断
在第三次作业中,我设置的线程类变为Input,TotalSchedule,SingleSchedule,NormalElevator,DoubleCarElevator五个类。
Input类和TotalSchedule类通过总请求队列waitrequest联系。TotalSchedule将waitrequest中的请求分配到SingleSchedule中,SingleSchedule根据当前状态(即为单轿厢电梯还是双轿厢电梯),向NormalElevator或者DoubleCarElevator分配请求。如果TotalSchedul向SingleSchedule分配的请求为双轿厢重置请求,则SingleSchedule会使NormalElevator进入结束阶段,并新开始两个DoubleCarElevator线程。
这次新增的一个需要重点考虑同步问题的地方就是双轿厢电梯的换乘。因此我单独设置了Flag类,用以完成双轿厢电梯的同步控制。对换乘楼层设置occupied标志位,如果有电梯抵达换乘楼层,将标志位置1,离开换乘楼层,将标志位置0。电梯能否抵达换乘楼层要根据occupied标志位的状态来决定。FLag类中有更改occupied标志的方法,均被设置同步控制。
本次作业由于出现了双轿厢电梯,因此我将原先的单层调度器模式,改变为了双层调度器模式。外层调度器将请求分配给内层调度器,内层调度器再将请求分配给电梯,从而能够较好的处理双轿厢的问题。
由于新增了双轿厢电梯,因此调度策略会发生不同的变化。当然random分配与均匀分配不会有太大改变,“接待最短时间”需要考虑是上轿厢还是下轿厢,“调参大法”需要更改参数设置。(影子电梯没有实现>_<)
本次作业出现了一个bug,是wait/notify问题。进过分析我发现,死锁的产生主要是发生在双轿厢电梯的换乘过程中。当两个电梯同时都要向换乘楼层前进时,先占据换乘楼层的电梯会进行换乘操作,另一个电梯会处于等待换乘的阶段。在进行换乘操作的电梯完成换乘后,会让出换乘楼层,并唤醒等待电梯。但是如果电梯抵达换乘楼层并不是进行换乘操作,只是接取换乘楼层的请求时,如果其将换乘楼层占领,另一个电梯如也想进入换乘楼层会进入等待,但是在换乘楼层的电梯在接取请求后不会将其唤醒,从而使等待电梯一直等待。因此需要在换乘楼层空出后,唤醒需要进入换乘楼层的电梯。
毫无疑问,每个作业的任务目标都是将所有的乘客送到目标楼层。其中一个核心内容就是乘客请求,这是我们需要处理的任务。在这三次作业中,除了第一次作业指定了运送电梯外(第一次作业的指定电梯更多是关于分配策略的变化),三次作业中乘客请求的主体内容是没有变化的,核心就是起始楼层与目标楼层,我们所需要完成的任务也都是将乘客从起始楼层运送到目标楼层。
第一次作业的线程结束条件很简单,就是看共享的请求队列是否为空
但是第二次作业加入了reset请求,使得结束条件的判断更为复杂,不仅仅需要考虑总请求队列,还要考虑尚未加入到总请求队列中的重置请求。
第三次作业加入了双轿厢电梯,带来的改变是换乘请求。如果换乘请求没有处理完,相应的电梯线程也不能进入结束阶段
第一次作业指定了运送的电梯,因此分配策略无需考虑
但是在第二次作业中不在指定运送电梯,因此需要我们自己设计分配策略
第三次作业中又加入了双轿厢电梯,因此分配策略需要做出相应的变化。调度器也从单层变为了双层。
第一次作业没有这个任务啊
第二次作业新增reset请求,带来了电梯属性的变化与重置请求的处理
第三次作业新增DoubleCarReset请求,带来了新的电梯种类,换乘请求的处理还有双轿厢电梯的运行策略
多线程程序最关键的一个问题就是线程安全,这也是导致我在编写程序中抓狂,最后程序出现问题的最主要原因。
在我看来,线程安全就是想要达到如果多个线程访问一个共享对象时,可以保证调用这个对象的行为都可以获得正确的结果。
为了达成这个目的,使用了两种方法,一种是synchronized关键字来对方法和代码块进行同步保护,另一种是使用了 wait() 和 notifyAll() 方法来进行线程间的等待和唤醒。(也要注意使用顺序等问题避免死锁的产生)
在这三次作业中,层次化设计也十分的重要
一个是在类的设计上,通过Input类,Schedule类和Elevator类相互协作来完成任务。
另一个是在方法设计上。将分配策略相关的方法,重置处理的方法,电梯运行等方法都进行拆解。使得线程类的run方法中只保留最基本的操作,使得结构更加清晰
第二单元的作业相比第一单元来说,代码量有所下降,但是思维难度有所上升,主要是第一次接触多线程程序的编写,一开始没有能够理清楚各个线程的关系,包括该把哪些类设置为线程类,什么应该作为共享对象等等这些问题,但在对任务进行细致的分析后也还是能够逐渐的搞明白应该怎么去设计,也对多线程有了更加深刻的理解。
但是对于这单元的作业也还是有着一些想要达到却未能尝试的地方,一个是尝试影子电梯的分配策略,一个是在使用synchronized关键字外尝试使用ReentrantLock,ReentrantLock更加的灵活,在处理某些同步问题时可能更有优势。主要也是因为时间问题,加上担忧引入新的问题而难以解决,我没有能够做到这些实现,也是有点小小的遗憾。
OO课到这里已经快走完一半了,也是在“攀爬昆仑”的过程中学到了许多,也希望能够在接下来的课程中继续加油!