OO第二单元总结

OO第二单元总结

1 第一次作业

1.1 同步块的设置和锁的选择

第一次作业的要求较为简单,其主要目的是让初学者了解多线程程序设计。在这一次作业中,我的设计中会涉及到线程冲突的对象为PassengerQueue,所以我的同步块都设置在类PassengerQueue中,然后在别处就可以不再思考同步问题,直接使用即可。因为刚开始接触多线程,为了避免出现问题,我偷懒地将每个方法都设置了synchronized。

1.2 调度器设计及架构分析

第一次作业中对于每一个乘客都指定了接送的电梯,所以对于调度器来说直接将乘客交给指定电梯即可,而需要设计的是电梯中对于分配的乘客队列将采用什么运行策略。根据往年的经验,我最终选择了Look策略。

至于调度器与输入线程的交互在于两者共享了一个PassengerQueue,输入线程向该队列中加入乘客,调度器取出乘客,同时调度器与每个电梯线程也共享了一个PassengerQueue,调度器根据乘客要求直接将乘客加入指定电梯的PassengerQueue,电梯根据其PassengerQueue接送乘客(取出乘客)。

其中uml类图为

在这里插入图片描述

其中,主线程会创建输入线程(InputThread),调度器线程(Schedule),六个电梯线程(Elevator)并运行。输入线程负责获得乘客请求,调度器线程负责将乘客请求分给电梯线程,复杂度主要体现在电梯线程中的电梯运行策略。

下面的uml协助图或许会更加清晰地展现线程间的协助关系。

在这里插入图片描述

1.3 bug及修复策略

在本次作业的强测和互测中均未出现bug,不过在互测环节发现了房友的bug,产生bug的数据为在49秒时输入大量乘客请求且只使用一部电梯,电梯运行策略较差的程序就会出现超时bug。

2 第二次作业

2.1 同步块的设置和锁的选择

第二次作业增加了重置请求,这对于多线程同步问题提出了更高的要求。首先,我增加了BasicRequestQueue类,即让InputThread和Schedule共享的waitQueue类型转换为BasicRequestQueue。在进行reset时,Schedule将相应电梯的isReset位标位ture,电梯在reset时可能会将乘客重写放回waitQueue中,这使得waitQueue也与所有电梯线程共享了,对于这一操作,通过在类BasicRequestQueue中增加同步块方法,在类PassengerQueue中新增深拷贝加清空的方法即可实现。

2.2 调度器设计及架构分析

第二次作业中每个乘客请求不再指定电梯,这就需要我们的调度器对于每个乘客请求选择合适的电梯,使得系统运行时间尽量短、电梯耗电量尽量少、每个乘客的等待时间尽量短。我的调度器选择了影子电梯,具体的实现思路是:对于一个乘客请求,将他加入一个电梯的parallelQueue,计算这个电梯将其中所有乘客(包含该乘客)送完所需的时间,比较每个电梯的时间,选择最小的那个电梯即为分配的电梯。具体实现也并不复杂,即模拟电梯的实际运行过程,在每一次要sleep时变为直接加上对应时间即可,不过需要注意的是模拟电梯中乘客队列的获得可能会与电梯中原本的队列产生冲突,我的办法是在PassengerQueue增加深拷贝方法,在电梯类中增加新的PassengerQueue用于在每一次while循环时获得电梯内的人(不同于parallelQueue,仅为一个Passenger的ArrayList),在模拟时间时拷贝该队列。但是可以发现这样似乎只考虑了系统运行时间而忽略了另外两个指标,我没有考虑的原因在于难以确定电梯的耗电量所产生的影响,不过事实证明即使这样性能分也已经足够了。

架构分析:其中uml类图为

在这里插入图片描述

这一次架构整体上并未改变,应reset需求增加Reset类和BasicRequestQueue类,应乘客分配需求增加Simulation类,Schedule线程在处理取出的请求时,若是乘客则先通过Simulation类计算出最小运行时间的电梯再分配,若是reset则传递给对应电梯相应参数。

uml协助图变化不大,如下所示

在这里插入图片描述

2.3 bug及修复策略

本次作业强测未出现bug,互测中被发现一个bug。该bug出现的原因在于终止条件的判断出了一点纰漏。我出现bug的终止条件是这样写的:

if (waitQueue.isEnd() && waitQueue.isEmpty() && !existReset()) {
                for (int i = 0;i < 6;i++) {
                    processingQueue.get(i).setEnd(true);
                }
                return;
            }

其中existReset()是判断是否有电梯在重置,这个bug其实就是课上所讲的check-then-act所导致的安全问题,只不过场景稍微复杂我就没有意识到了而已。试想,先判断waitQueue为空且此时电梯在重置,然后电梯重置后将乘客加入waitQueue,此时waitQueue不为空,但再判断!existReset()为真,所以会直接结束那个刚刚重置的电梯(因为其中没有乘客了,其他电梯会运行到没有乘客),使得分配给该电梯的乘客无法到达目的地。修复策略也很简单,即先判断!existReset(),如下:

if (!existReset() && waitQueue.isEnd() && waitQueue.isEmpty()) {
                for (int i = 0;i < 6;i++) {
                    processingQueue.get(i).setEnd(true);
                }
                return;
            }

面对多线程的debug方法:先根据错误的输出思考是哪一部分行为的错误,对于这一个bug为,1、发现乘客未送达,2、发现电梯6不再工作;然后思考乘客请求所有可能的流动方向和导致电梯6线程结束的原因;最后锁定到该终止条件的判断。虽然说得轻巧,但这个bug的修复也花了我不少时间。

3 第三次作业

3.1 同步块的设置和锁的选择

第三次作业增加了双桥厢电梯及其重置请求。在这一次作业中,我发现了如上一次作业中的终止条件的判断可能会导致CTLE,即在有电梯重置且waitQueue为空时会轮询,但最后并没有出现,可能是程序逻辑相对简单轮询时间短吧。但在这一次作业中就出现了CTLE错误,所以我增加了Counter类,其是线程安全的,我以所以乘客全部送达为终止条件,在有电梯重置且waitQueue为空时让计数器wait,直到增加Counter或减小Counter使其唤醒。

3.2 调度器设计及架构分析

这一次调度器实现影子电梯比上一次作业复杂,理论上是通过二次查询加二次分配来解决双桥厢电梯问题,在计算双桥厢电梯的运行时间时,若乘客不能直接送达,则不妨假设该乘客会被同一个编号的另一个双桥厢电梯最终送达来计算出运行时间进行第一次分配,当乘客到达换乘层时再让调度器进行分配(此时不一定分配给同一个编号的另一个双桥厢电梯)。这个应该是比较完善的影子电梯了,不过对我还是有些难度,最后我选择在计算双吊厢电梯的不能直接送达的乘客时直接加一个参数,最后的性能也还行。

架构分析:其中uml类图为

在这里插入图片描述

这一次架构也没有大的变化,新增的DoubleCarReset类为双吊厢电梯reset请求,Counter类为计数器,Flag类是避免双桥厢电梯同时进入换乘层。更改较多的类为BasicRequestQueue类和Elevator类,主要是完成双桥厢电梯。综合考量,我的作业中变化大多聚集在新的需求上,整体的架构并没有改变。

uml协助图和上一次作业没有任何变化,唯一可能的不同是电梯线程数可能大于6,因为我的双桥厢电梯的实现为关闭原来的电梯线程,新开两个电梯线程。

3.3 bug及修复策略

这一次作业强测和互测均未出现bug。

3.4 双桥厢电梯避免相撞

新增Flag类,其是线程安全的。当双桥厢电梯要进入换乘层时会尝试获得flag的锁,若得到才能进入,否则等待。当双桥厢电梯进入换乘层且完成操作后会主动离开换乘层放弃flag锁,以免影响另一部双桥厢电梯进入。

4 心得体会

4.1 线程安全

理论课上所说的read-modify-write和check-then-act计算模式能概括我所面对的几乎所有线程安全问题,避免线程安全问题的方法就是对每个共享的变量的任何操作和判断都要足够谨慎。这个单元结束我也没有遇到过死锁问题,可能是因为我将那些共享队列封装的较好,在使用时不用思考锁的获取顺序,自然较难发生死锁问题。经过一个单元的磨炼,我对于多线程程序设计的思想有了更深的理解。

4.2 层次化设计

在这一单元我的设计中体现层次化的地方不多。例如BasicRequestQueue类中的请求可能会包含乘客请求、普通重置请求和双桥厢重置请求。这一单元中的各个线程类更多的是逻辑先后关系和并列关系。

层次化设计

在这一单元我的设计中体现层次化的地方不多。例如BasicRequestQueue类中的请求可能会包含乘客请求、普通重置请求和双桥厢重置请求。这一单元中的各个线程类更多的是逻辑先后关系和并列关系。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值