OO第二单元总结

  在第二单元主要训练了我们的多线程编程和设计能力。

第一次作业

总体设计

  在第一次作业中,我采用了生产者消费者模式。在主线程里读入数据,将请求放入请求队列里。我在请求队列的设置里设置了五个Arraylist,分别对应着五个楼层。之后电梯作为消费者完成请求。

在调度策略方面我采用的是LOOK策略。其实在最初的时候,我才用的另外一种调度方案。他与LOOK策略的不同主要体现在没人的时候的选择上:

  在LOOK策略中,当电梯内部没有人时,会先看原先的前进方向上有没有请求,如果有,电梯依旧会在原有方向上前进。

  在我最初的调度方案中,当电梯内部没有人的时候,电梯会找到队列中离自己所在楼层最近的请求当作主请求。

  这两种方案一般而言还是LOOK更快,我在最初测试的时候有的随机构造的用例最初的调度方案就比ALS基准策略慢。于是我就换用了LOOK策略。就像荣老师说的那样,这个问题没有最优解,我们只能找到一种在大多数情况较为优秀的调度方案。再加上由于往届的学长大多采用的也是LOOK策略,于是我最终采用的是LOOK方案。

调度器和锁的设置

  我的主调度器就是WaitingQueue,当放入请求后通过查看输入请求的frombuilding直接放入对应的5个请求队列。电梯内部有自己的调度器,负责检索请求队列并提供给电梯目标楼层。这样的交互较为简单。

  在锁的设置方面,我在电梯和输入线程之间的五个共享队列加锁了。每当生产者将请求放入队列时以及消费者需要从队列里取出请求放到自己内部的队列里时(就是“上人”),都需要加上锁进行同步。具体到代码上,就是在WaitingQueue的set方法内、电梯内部的策略方法内以及in、out对对应的请求队列加锁。同时为了保证输出的线程安全,我在官方提供的输出包加了锁,

类的说明      

  本次作业有四个类,MainClass主类主要负责读取输入,WaitingQueue主要作为生产者和消费者之间的托盘,Elevator负责运送乘客,完成请求,Output为输出类,就是为输出包提供了线程保护。

类图与协作图

 

 

第二次作业

总体设计

  这次作业增加了跨楼层的横向运输的要求。这就需要我创建一个横向电梯的类,乍一看,横向电梯与纵向电梯都是电梯,可以让他们都继承于同一个父类电梯类,或许可以减少大量的代码编写量而且易于扩展,但是我在实际操作中发现,对于我的代码而言其实并没有简单多少,反而大大增加了电梯类的封装和封闭性。原因有以下几点:

  首先,在安装好父类以及子类之后,我的纵向电梯与横向电梯运行之后所在楼层的计算方法不一样,横向电梯需要进行取模运算。导致许多方法的许多地方我都需要改变,同时由于横向电梯可循环的性质导致我在寻找目标的时候的计算方法也不同,这就导致了我两个电梯的两个主要方法都不能有效的复用。

  其次,如果分出父类与子类,我在电梯实际运行时需要不断的获取自身的内部人员队列、所在楼层等等信息,同时策略方法无法写在父类里——横向电梯的循环运行和纵向电梯的策略不一样。因此,就需要在父类里写出许多get或者set方法,导致封闭性大大降低。因此我只是单独加了一个横向电梯类。

  在策略方面,对于多个电梯共同在同一楼层或者楼座的新情形,我采取了自由竞争的方法。原因有以下几点:

  首先,采取自由竞争,我的代码改动很少,不易于出现bug,而且性能方面也有一定的保证。

  其次,如果采取根据电梯状态进行分配的方法,我就需要在主调度器内获取电梯的一些信息,这减少了电梯类对其他类的封闭性,同时阅读了往届学长的博客之后发现如果忘记考虑了某些因素很有可能会导致某个电梯的任务过多,导致RTLE,我认为要首先保证正确性再去看性能,所以就选择了自由竞争。

  对于横向电梯的策略,我最初也想使用的是LOOK算法,但是后来写完之后忽然发现了一种情形,如果再A、C、E三座都有与电梯目前前进方向相反的请求,那么我改写的LOOK策略会产生死循环。于是我又换回了我第一次作业的备用方案。

调度器和锁的设置

  我的调度器与第一次作业没有变化。

  在锁的设置方面,我为每一个楼层、每一个楼栋设了队列。其余仿照着第一次作业,在共享的队列进行读取以及增删元素的时候加锁。

类的说明

  与上次作业相比,新增了横向电梯类。

类图与协作图

第三次作业

 总体设计

  这次作业的电梯信息可订制,尤其是横向电梯有可停靠楼层的信息,同时完成人员请求需要换乘。对于前面的新增要求只需要修改电梯的有关构造函数和相关函数中的信息即可。对于换乘请求我新增了People类:

  新增的People类用来存储请求有关信息。其内部含有人员当前所在楼层楼座、当前的目标楼层楼座以及对应的初始请求。通过这样的处理我可以兼容前面我已经写好的代码。在之前的作业中,由于每个人最多只乘坐一次电梯,对应的目标楼层或楼座就是tofloor和tobuilding,我的电梯可以看作是把人员送到目标楼层、楼座。因此这次作业中可以借助前面的代码,在放入请求队列之前预设好目标楼层、楼座,之后送入请求队列中,之后人员在从电梯内部出来的时候再判断是否到达目的地,如果没有再送回主调度器。

  在策略方面,我使用的是课程组提供的换乘策略,同样的原因,要实现选择更好横向电梯来达到总体更高的性能的话,我还是要暴露许多电梯内部的信息,同时容易出错。

  在这次作业中,由于对于WaitingQueue要传入各个电梯内部,所以我尝试了单例模式,但是由于单例模式需要加锁,在找bug的阶段我出现了死锁状况,刚开始以为是单例模式的缘故就将单例模式删去了。

调度器和锁的设置

  由于我新建了People类,在电梯运行方面与之前两次作业完全兼容,所以调度器方面没有什么变化。只是在电梯出人之后判断并将没到达目的地的人员送回主控制器。

  在锁的设置方面,除了第六次作业所加的锁之外,我又为一个变量count加了锁。变量count表示目前所有请求还需要乘坐几次电梯。我设置这个变量的主要原因就是为了结束线程。在过去两次作业,电梯没人、请求队列没人、输入结束就代表当前线程停止,但是这次作业由于无法或者较难判定在其它电梯内部的人是否之后要乘坐某部电梯,所以只能在所有请求剩余的乘坐次数为0的时候结束线程。在输入线程放入请求时将count增加,放入电梯的请求队列时count减少。

类的说明

  与上次作业相比,新增了People类

类图和协作图

扩展性分析

  根据我第三次的代码,在请求方面扩展性好,但在设计到电梯方面我的扩展性较差。比如如果某一个人想在某一特殊楼层停一下取个东西,再接着走等等。但是对于电梯而言,如果想为某个乘客设置VIP特权,或者某个乘客只想做某个电梯,我的改动量就较大了。

Bug分析与互测

  这一单元我搭建评测机,三次作业在强测以及互测都没有发现bug。

  在互测方面,我主要利用评测机跑大量的数据来发现其它同学的问题。第一次作业我用评测机hack了三个人,第一位同学电梯有时会到达A座0层,第二位同学有时电梯会瞬移。第三位同学的bug由于没法做到每次都能复现,所以我交了一次没有hack到就作罢了。第二次作业抓了两个人,可无奈都是20次能够复现2-3次,没有成功抓到人。第三次作业没有发现其它同学的bug。

  这一单元的评测最大的不同,是程序运行过程中也要保证正确。在这一点上需要我们对输出结果构建状态机来分析正确性。

心得体会

  这一单元让我初步了解并实操了多线程编程,让我进一步的理解了面向对象的思想。同时在理论课上,我们学到了SOILD原则,在下一单元我要尝试将这一原则贯彻到编程中。遗憾的是,这一单元我尝试的单例模式并不成功,在之后我也要适时使用有关的模式。在测试方面我也开始编写评测机,这也是一种对能力的锻炼。我期待着下一的单元我能够有新的收获。

 

posted @ 2022-04-26 22:15  旅行者空  阅读(45)  评论(1编辑  收藏  举报