BUAA_OO_第二单元作业总结

BUAA_OO_第二单元作业总结

前言

第二单元,是关于java多线程的学习,基于完成“电梯”载客需求进行。本单元的重点在于实现多个线程的安全运行,并且了解有名的“生产者-消费者”模式。与第一单元的作业不同,我们在写代码时,考虑的不只是Object(对象),还要考虑Thread(线程),也就是说,我们要考虑代码执行的中间状态。总体来说,第二单元的迭代作业比第一单元轻松,但是需要仔细考虑线程的安全问题,否则容易导致TLE,CTLE等令人无从下手的问题(小概率的死锁比大概率WA的情况糟糕得多)。

一.多线程协同的设计策略

1.作业要求

  • 作业一:实现一个电梯,载人数不限,在乘客请求随机到达的情况下,需要在规定的时间内结束载人任务。特点:载人数不限,可到达楼层不限,规定时间基于ALS算法。
  • 作业二:在作业一的基础上,规定电梯数量,在规定时间内结束载人任务。特点:在最开始规定电梯数量,楼层出现不连续性,规定时间上限200s。
  • 作业三:在作业二的基础上,规定初始电梯数量,并且可在中途加入新的电梯,可到达楼层不连续,不同种类电梯运行时间不同。特点:初始化给定三类电梯,在运行中途可以加入新的电梯,不同种类电梯可以停靠的楼层不同,不同种类电梯运行时间不同,规定时间上限200s。

2.设计策略

  • 我三次作业采用的都是“生产者-消费者”模式。
  • 创建三类线程:MainClass主线程,ElevatorExec电梯线程,ElevatorInputReq输入请求线程。MainClass主线程负责创建其余线程,ElevatorExec线程模拟真实的电梯运行(乘客的进出,楼层的到达),ElevatorInputReq线程模拟请求接收器(等待随机的乘客请求)。
  • 电梯线程和请求线程的共享对象--RequestList。电梯线程和请求线程通过RequestList来实现通信,相应的,为了实现线程安全,RequestList中的共享变量需要加锁。实际上,我采用的是给RequestList对象上锁(这也是老师推荐的方法)。

3.调度算法

  • 调度算法关系到性能分,也关系到能不能在给定时间将乘客送达。
  • 性能分标准:作业一采用与ALS算法比较的方式,作业二和作业三采用全体竞争方式(说白了就是大佬虐菜鸡)。
  • 前两次作业我采用的调度算法是贪心,判断是否需要捎带的依据是:捎带后的运行时间与不捎带的运行时间。第一次作业得分99,第二次作业得分94,看来我自己写的贪心还是太low(菜),所以第三次作业我选择scan算法,得到了98分。另外,我采取的是电梯“抢”请求的原则,也就是“先到先得”&“scan”。

二.SOLID分析

SRP--单一职责原则

  • 内容:就一个类而言,应该仅有一个引起它变化的原因。
  • 分析:主要的类是RequestList,ElevatorExec,ElevatorInputReq。其中,RequestList类负责调度乘客(保存和分配乘客请求),ElevatorExec类负责运输(乘客进出,到达楼层),RequestInputReq类负责读取乘客请求。总体来看,每个类的职责分明,相互之间没有重合的职责。我认为符合SRP原则。

OCP--开放-封闭原则

  • 内容:软件实体(类,模块,函数等)应该可以扩展的,但不可修改。
  • 分析:我的代码中,主要的类中的主要方法可以通过继承来实现扩展。比如修改新的调度算法,或者增加新的电梯种类,行为等。在两次迭代作业中,我对代码的修改也只是改变了调度算法以及线程的run()方法。

LSP--替换原则

  • 内容:子类型必须能够替换它们的基类型。
  • 分析:我在三次作业中,未用到继承。三种类型的电梯,也只是通过构造方法的参数来实现。

DIP--依赖倒置原则

  • 内容:抽象不应该依赖于细节,细节应该依赖于抽象。
  • 分析:电梯类和调度器类需要相互依赖。

ISP--接口隔离原则

  • 内容:不应该强迫客户依赖与它们不用的方法。接口属于客户,不属于它所在的类层次结构。
  • 分析:代码中未使用接口。

三.基于度量分析代码

作业一

UML图

Sequence UML图



度量分析


整体来看,因为RequestList类需要执行调度任务,ElevatorExec类需要执行接送乘客任务,复杂度都很高。

作业二

UML图

Sequence UML图



度量分析


由于在第一次的基础上,楼层的不连续性,以及电梯的数量规定,电梯线程类变得更加复杂。

作业三

UML图

Sequence UML图



度量分析


由于第三次采用了更加简单的scan算法,调度器类的复杂度下降了。

四.分析程序bug

  • 这三次作业中,强测和互测均未被测出bug,这很大一部分归功于我自己写的测评机。
  • 在我自己的测试中,出现过一个bug。那是在第三次作业:某个乘客需要换乘,当他在换乘楼层换下后,已经没有电梯在运行了,这时这个乘客就到达不了目标楼层。我的解决办法是,让所有电梯最后一起结束,这个结束标志要考虑:1.当前是否还有输入请求;2.当前请求队列是否还有请求;3.前两个条件都满足时,是否还有乘客在换乘的路上。
  • 感想:动手写出测评机才是真正的完成了一个程序。

五.互测策略

  • 有了测评机,就可以批量检查程序的正确性。
  • 我采用的是随机生成数据的策略。(有人也使用手动生成更加魔鬼的数据,比如50条id-FROM--3-TO-20,我还是没下得去手)
  • 经过测试,我发现被hack的程序都是TLE或者CTLE。看来这种小概率发生的事件是测评机也不能完全杜绝的,唯一的办法就是在写代码时仔细考虑加锁问题。

六.体会

关于线程

  • 线程安全问题不容忽视,忙等,轮询,死锁都是致命错误。还是记住老师的建议:锁住对象。利用锁住对象的方式,我得以避免了许多安全问题(也许还有没测出来的问题)。
  • 关于线程的结束。在第三次作业,我检查并发现了线程的结束问题,这也是需要我们仔细考虑的方面。

关于测评机

像这种不确定的线程程序,没有一定的测试量是不行的。写完程序后,通过了样例并不代表程序就正确了,至少要测试一定数量级的数据才能稍微放心(但是这也不代表程序一定是正确的)。拥有一个测评机是最好的选择。

猜你喜欢

转载自www.cnblogs.com/xishufan/p/12704646.html