菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
76
0

2021-OO-第二单元总结

原创
05/13 14:22
阅读数 48543

第二单元总结

在对本单元的内容进行分析前,让我们先回顾一下本单元的大致结构:生产者-消费者模式,对应到我们的电梯系统,也就是请求-电梯模式。请求通过IO输入,经过缓冲区waitingQueue,以一定的规则分配给电梯进行运送(也就是我们所说的调度)。这个大致的结构可以被概括成以下这个图表,聊以在没有进行结构分析前,进行背景铺垫。

 

 

一、同步块的设置和锁的选择

同步块的设置

同步块的设置是为了保证所有临界区可以实现互斥访问,即在多个线程访问同一块共享资源时,可以避免由线程不安全导致的数据错误以及异常错误。因此,存在临界区的地方,为了保证线程安全性,就必须设置相应的同步块。

而在本单元的作业中,涉及到数据读写的部分,在生产者-消费者模式下,应当是他们的共享数据:生产者不断生产需求,消费者获取需求并进行解决(其实也是实验课上提到的客户-工人模式)。

所以在本单元作业中,我选择将同步块设置在调度器和处理IO线程共用的缓冲区。与此同时,我还分别将电梯的载客容器sendingQueue、电梯的待处理请求队列processingQueue设置为线程安全类,避免大量同步块使代码可读性、可维护性、可扩展性下降。

除了保证线程安全以外,同步块还可以用来避免轮询。我们的调度器并不是在一个时刻获得的所有请求,而是时刻等待着输入。但如果时刻保持着线程的苏醒,就会进行轮询,大量占用了系统资源,通过设置同步块,我们可以调用同步对象的wait以及notify方法,在没有可用资源时,让一部分线程睡眠,从而避免忙等待。

锁的选择

我们在课堂上主要涉及了两种锁的选择。

第一种便是通过synchronized语句来在特定代码区块中为指定对象进行加锁,这种方法较为简便通用,只需要将synchronized(Object)中Object替换成你想要施之以锁的对象就好了,在其中代码段执行notify语句、执行完毕时或是线程发生异常时JVM会释放该锁。但对于同一代码段加入多重的synchronized块可能会导致死锁现象,也会使代码变得不可描述(。

第二种则是lock&condition组合。我们可以实现我们特定的锁,比如读写锁。对于我们想要实现的线程同步与互斥,其实可以做剪枝优化的。比如只有在不同线程进行读-写、写-写时,才需要我们设置临界区进行同步互斥,当读-读时我们并不需要加锁——这会或多或少降低我们程序的性能。因此我们可以实现读写锁,在需要加锁的代码段前加一句Object.lock(),也可以通过条件语句Condition.await()/signal()(可以唤醒指定线程)进行类似wait()/notify()(只能随机唤醒线程)一样的操作。

在竞争激烈的情况下,lock的性能表现更优,但是抵不过synchronized更省事(bushi

在本单元的作业中,我选择了实现简单、效果稳定synchronized块,从而更好的将注意力放在电梯的设计和调度上。

二、调度器设计

功能设计

为了程序的可扩展性,本单元作业我从第一次作业就开始着手编写调度器。在三次作业中,调度器的任务从简单的处理缓冲区与任务处理区的关系(第一次),再到简单的单一类型电梯的调度(第二次),最后到对于不同类型电梯的较为复杂的换乘调度(第三次)。

最开始的调度器就是一个简单的缓冲区,将主请求队列中,取出楼层最高的的请求传递到对应电梯的待处理队列中。

第二次的调度器在增加电梯的条件下,将当前请求分配给不同的电梯。主要原则是保证每个电梯都能动起来、每个电梯中请求队列+运送队列人数均匀。

第三次的调度器在第二次的基础上,对调度器进行了扩增,将请求先分配给C,如果C不符合相应条件,再分配给B,如果B也无法运载,再分配给A。

调度器与线程的交互

调度器与线程的交互主要是读取IO线程、电梯线程的共享数据,从而做出一些反应。在调度器中,通过共享主请求队列以及每个电梯的信息,我们可以实现一定的调度分配策略。在根据一定的策略分配信息以后,我们还能够通过这些共享数据来为其他线程加入一些请求。 主要分为以下两类:

1.调度器线程 从IO线程中获取新请求

2.调度器线程 向电梯线程中投放请求

三、可扩展性

UML类图

 

 

UML协作图

 

 

扩展性

让我们通过四个可能的需求增加来考虑可扩展性:

假想1:扩展电梯种类

通过增加Elevator类的子类进行扩展,可扩展性好。

假想2:增加电梯功能

通过Function扩展接口实现

假想3:实现其他策略

在调度器中增加相应的条件语句(可扩展性差

假想4:修改部分类型电梯的部分功能

需要修改原来代码(不符合开闭原则(但我觉得情有可原...

总的来看,通过继承和接口(java多态)的运用,在本单元作业中,程序的可扩展性还是得到了较高的提高(相较于第一单元),但策略类还是没有实现较高的可扩展性。

四、bug分析

未通过的公测用例

第一次作业:一个Night模式下的测试点超过了ALS策略所需时间。

第二次作业:一个Random点在170s才结束输出,自己程序不够快结果超过了210s (RTLE)

第三次作业:换乘时候开关门忘记加一个if语句,强测挂了一半(悲)

互测被发现的bug

感觉本单元大家互测的积极性都不高(可能因为冯如杯OS比赛课程之类的压力比较大吧)

前两次互测侥幸逃过一劫

第三次互测 人在C Room 大家都躺平

五、bug查找策略

测试策略

本地测试

除了第三次作业以外(偷懒没测结果就悲剧了呜呜呜),对于其他的作业尤其是刚开始的作业,做了一些基础的功能测试,来测试IO线程和调度器对于同时输入的反应和电梯的基本功能。可以拿别人的评测机进行评测,但感觉意义不大,自己很容易沉迷到那种使用黑盒的变态快感中(误。

互测

第一次作业还做了一次尝试,后来两次作业就没有再管互测,大概是下载同房间同学的代码静态分析一遍后发现看不出什么问题(。。)就跳过了。

线程安全相关问题

在中测的时候遇到了很奇怪的问题,在研讨课上纪老师称之为”薛定谔的bug“。在第三次作业中测的过程中,有一个测试点出现了RTLE,但是在再次提交的时候就通过了测试样例,且强测中并没有出现该种情况。奇怪的是我的所有容器类,要么全部实现了线程安全方法,要么调用的是java库函数中的BlockingQueue、ConcurrentHashMap等线程安全类...

与第一单元的差异

第一单元的测试样例和测试结果的生成和获取门槛都比较低,大家更容易参与进来。多线程的输入输入处理和bug判定对于我来讲还是门槛有点高。还有就是大家都不刀的话,会产生一种心理效应,对于群体来讲,如果达成了某种共识,比如多线程互测比较难,刀很多刀也刀不到人,那么后来者(比如我)可能也就跟风随流了。不过这单元比较令人开心的是,自己学会了阅读别人的代码,学习到了很多东西,感觉还不错。

六、心得体会

线程安全

为了保证线程安全,我将sendingQueue、WaitingQueue等对象实现为线程安全类。并在IO线程、调度器获取请求、每个电梯获取请求时设置了synchronized块,在其中设置wait()来避免线程不安全的情况。其实脱离作业来讲,线程安全核心还是同步互斥问题,对于共享数据的管理问题。在本单元中,我们主要学会了同步块、读写锁等方法,在OS课学习理论的时候能够更加容易理解(确信)。

层次化设计

在本单元中,弥补了上一单元没有怎么使用继承和接口的遗憾。通过上面的UML类图可以看出,相较于第一单元更多地使用了多态。设计更加合理和清晰。美中不足的是代码本身并不简洁,过多因为数据结构选择不合理导致的流处理的使用使代码十分丑陋。以后对于数据结构的选择会更加慎重。

 

 

 

发表评论

0/200
76 点赞
0 评论
收藏
为你推荐 换一批