2022北航面向对象第二次作业分享及总结

2022北航面向对象第二次作业分享及总结

本次作业由三次子任务组成,从3月28号开始,迭代开发,实现了一个多线程实时电梯系统。本次项目是笔者第一次接触多线程开发,在迭代过程中逐步熟悉了多线程的设计方法。本文将会主要从架构设计、线程安全以及策略问题三方面进行分析总结。

题目概述

本次作业旨在模拟多楼层建筑中的电梯系统。

  • 第一次作业
    电梯类型:纵向电梯(统一参数)。
    可否中途增加电梯:否。
    电梯之间是否存在协同关系:否。
  • 第二次作业
    电梯类型:纵向电梯(统一参数)横向电梯(统一参数)。
    可否中途增加电梯:是。
    电梯之间是否存在协同关系:否。
  • 第三次作业
    电梯类型:纵向电梯(自定义参数:移动一层花费的时间、限乘人数)横向电梯(自定义参数:移动一座花费的时间、限乘人数、可停靠楼层)。
    可否中途增加电梯:是。
    电梯之间是否存在协同关系:是。

架构设计

三次架构统一采用生产者-消费者的设计模式,同时为了避免轮询的发生,统一采取了wait-notifyAll的方法。

生产者消费者模式是多线程中最为常见的模式:生产者线程(一个或多个)生成面包放进篮子里(集合或数组),同时,消费者线程(一个或多个)从篮子里(集合或数组)取出面包消耗。虽然它们任务不同,但处理的资源是相同的,这体现的是一种线程间通信方式。
在这里,我们把篮子称为托盘,代表生产者和消费者共同处理的资源。

第一次作业

第一次作业通信关系较为简单,只在调度器分配请求与电梯接受请求之间存在数据共享,这里的托盘即所有的乘客请求。
这里的调度器代码逻辑十分简单,即根据请求面向楼座将请求分配给对应的请求队列。

值得一提的是,在电梯的设立中可以采用工厂模式,方便实现不同运行策略的电梯装配,但由于实现成本较高,并且在本次任务中不涉及不同策略的调配,因此没有采用工厂模式进行构建。

UML图

第二次作业

第二次作业相比于第一次作业增加了横向电梯,但总体上对于横向电梯的处理跟纵向电梯并无差异;本次作业需实现在过程中增加电梯的需求,即存在多部电梯处理同一请求队列的状况,因此第二次作业相比于第一次作业出现了更多对于队列资源的保护问题。

由于出现了两种不同类型的电梯,因此我实现了针对两种类型电梯的调度器,以降低方法复杂度,同时鉴于两种电梯只是采取的策略不同,基本参数与主要行为都大致相同,为实现高内聚低耦合,建立了一个父类Elevator,使横向电梯与纵向电梯实现Elevator的泛化。
UML图

第三次作业

第三次作业增加了个性化电梯,同时对于横向电梯而言增加了可停靠楼层这一属性,并且对于请求的限制也放开了,解除了原先的单向请求限制,即需要实现横向电梯以及纵向电梯的协同来处理一个请求,因此第三次作业真正提出了线程协作的问题。
同时,本次作业我遇到的一个麻烦的问题就是在于何时终止电梯,我的停止逻辑设计为 当所有请求都被处理完毕 后统一终止所有线程。起初我让电梯对其余请求队列进行反复查询,直到全部为空,这显然会导致轮询的产生,使我的cpu时间极度膨胀。最后,我采用信号量方法解决了这一问题,我会在下一部分进行阐述。

UML图

总体时序图

main方法时序图

电梯运行时序图

请求调度时序图

线程安全问题

锁的选择

锁的目的在于解决资源占用的问题;保证同一时间一个对象只有一个线程在访问

本次设计中采用了java中的synchronized机制
synchronized锁是一种互斥的悲观锁,优点在于实现较为简单,然而不免有效率低下的缺陷所在,主要原因在于synchronized关键字是不可中断的,这也就意味着一个等待的线程如果不能获取到锁将会一直等待,而不能再去做其他的事了。同时,synchronized锁缺乏灵活性,加锁和解锁的时候每个锁只能有一个对象处理,并且一个对象同时只能拥有一把锁,这在之后的细节实现中会带来不小的设计麻烦,同时也与分布式的思想格格不入。

线程安全问题

  • 资源共享

    • 请求调度器添加请求到相应队列与电梯处理请求的资源共享问题
    • 同一楼座或者楼层全部电梯对同一请求队列进行竞争的资源共享问题
      采用对象锁的方式,设置队列内部的添加请求方法与删除请求方法为临界区,实现资源保护。
  • 线程协同

    • 第三次作业中当纵向电梯(横向电梯)将一个请求送到中转楼层(中转楼座)后,将请求放入到对应的请求队列中供另外的电梯线程进行处理。

      两种解决方式

    1. 当电梯请求队列为空时进行挂起操作(suspend),另一个电梯在传递完请求后进行唤醒(resume)。然而这种方法已过时,原因在于suspend()和resume()必须要成对出现,否则非常容易发生死锁。 因为suspend方法并不会释放锁,如果使用suspend的目标线程对一个重要的系统资源持有锁,那么没任何线程可以使用这个资源直到要suspend的目标线程被resumed,如果一个线程在resume目标线程之前尝试持有这个重要的系统资源锁再去resume目标线程,这两条线程就相互死锁了,也就冻结线程。

    2. 当请求队列为空且不需要再运行电梯时wait(),加入请求后notifyAll(),即”wait-notifyAll“模式

    本次我采用了第二种解决方法。

    • 第三次作业中需要实现如下行为,当所有请求都处理完后关闭所有线程。在处理这个问题时我的程序陷入了无限轮询,在寻找解决方法时屡屡碰壁,最后采取了信号量的方法解决了此问题。

    信号量具体操作:
    当线程完成一次对该semaphore对象的等待(wait)时,该计数值减一;当线程完成一次对semaphore对象的释放(release)时,计数值加一。
    当计数值为0,则线程等待该semaphore对象不再能成功直至该semaphore对象变成signaled状态。
    semaphore对象的计数值大于0,为signaled状态;计数值等于0,为nonsignaled状态。
    其中,增加信号量的数值的操作一般称作V操作,减少信号量的操作一般称作P操作。
    注意,两个操作都是必须是原子化的。

    我的解决方法中,当一个请求已经被全部处理完后,调用RequestCounter的release方法,代表处理完一个资源(资源释放);在所有请求输入完成后,记录请求个数,并调用acquire方法查收资源,acquire方法在无资源可查收的情况下会始终等待资源的释放。等到全部资源都已被查收后,设置队列结束信号,实现线程的终止。

  • 输出线程安全

本次工具包提供的TimableOutput.println()方法是线程不安全的,有可能出现以下情况:

Thread1获取时间戳
Thread2获取时间戳
Thread2将输出打印到控制台
Thread1将输出打印到控制台

/*输出*/
[   1.3190]IN-1-A-1-23
[   1.3180]IN-2-A-2-25
## 时间戳不递增

输出时间戳非递增的情况,显然我们是不希望这种情况发生的。于是我对该方法实现了封装,并手动加锁,实现了输出的线程安全。

策略问题

对于电梯的运行策略,我采取了大家广泛使用的look策略,并加以“时间预测方法”进行优化。

  • look策略
    即在某一方向运行时,若当前层有请求,且请求的运行方向与当前相同,则开门接收乘客。若电梯内没有乘客,且前方没有新请求,则转向。

  • 时间预测方法
    譬如,当前电梯要将主请求电梯从5层向下运行时,判断反方向两层是否有向下的请求。若有,则让电梯违反当前运行方向,前往该楼层接上乘客。(这种调度策略显然违背了现实生活中的电梯运行逻辑,但经过反复测验会对程序运行速度有显著提高

对于电梯的竞争策略,我采用了自由竞争的模式,有以下几个考虑

  • 1.电梯运行时间都很快,因此采用有策略的调度模式并不会对整体性能有太大提升。
  • 2.如果设计相应算法进行请求调度,无疑会提高代码复杂度,同时过于复杂的调度策略也会大大增加cpu运行时间。

综合考虑设计调度算法的性价比不够高,因此本次没有加以实现。(主要是自己菜懒)。

Bug与性能

  • 第一次作业在look策略的实现过程中产生了一个巨大的漏洞,导致我的电梯在每层最多只能接上一个乘客,因此我本次作业在强测过程中出现了部分数据点的超时;同时,我忽略了输出线程不安全的问题,出现了输出时间戳非递增的bug。
  • 第二次作业我在总结教训汲取经验后进行了整体代码的重构,并且重新审视了我的电梯运行策略,同时实现了“时间预测”,因此这次作业我的强测分数拿到了98.7,并侥幸没被hack。
  • 第三次作业没有产生bug,但是性能却很差,主要原因在于我在处理横向请求时没有实现横向电梯间的换乘,这点属实是自己没有考虑周全。

心得体会

早有耳闻oo的电梯单元十分折磨,这次算是体会到了。
个人体会三次作业难度排序为1>3>2,困难主要源自于初接触多线程,很多地方考虑的不够全面,尤其是在处理线程安全问题时屡屡碰壁,在多次尝试后才逐渐体悟到线程安全问题的解决思想。
另外就是这几周自己确实是太忙了,一直操心准备校歌赛,一定程度上也导致了自己对于本次作业的上心程度远不如第一次作业。
值得庆幸的是,这次作业的代码量没有那么大,所以这段时期也没有像前三周那样刷夜频率这么高。


部分内容借鉴自讨论区以及网络文章,在此致谢。

第二次作业毕

posted @ 2022-05-01 00:56  Chenkit^^  阅读(148)  评论(0编辑  收藏  举报