BUAA OO第二单元做业总结

1、做业设计策略

(一)第一次做业设计方案

  • 模型:生产者消费者模型
  • 两个线程:输入线程(生产者)、电梯线程(消费者)
  • 共享对象:请求队列
  • 退出模式:输入线程读到null,退出run,并将null传入请求队列,电梯线程取出null,即退出

第一次做业相对比较基础,个人设计上输入端向请求队列类添加请求、电梯线程从请求队列中依次取出请求,两个动做互斥,所以我对请求队列类方法上锁,解决写写冲突问题,保障线程安全。安全

关于程序结束的实现,因为此次电梯处理请求遵循FIFO原则,且一个请求仅会被添加到请求队列一次,所以文件结束标志null能够做为线程结束的flag。多线程

(二)第二次做业设计方案

  • 模型:生产者消费者模型
  • 两个线程:输入线程(生产者)、电梯线程(消费者)
  • 共享对象:请求队列(调度器)
  • 退出模式:输入线程退出方法同第一次做业,在请求队列类中设置end标记,电梯使用者队列为空且请求队列到达End状态退出

第二次做业再也不是无脑套用生产消费模型,电梯捎带需求对于电梯使用者队列更新与处理提出要求。我在请求队列的getRequest方法中实现电梯捎带,即将电梯当前楼层,电梯运行方向信息做为参数传入该方法,每一次电梯到达一个楼层(完成电梯任务或仅作Arrive动做),getRequest方法都会遍历请求队列,将可捎带请求加入电梯使用者队列。架构

关于电梯任务完成的优化,电梯的“出人”方法中,每一次要遍历使用者队列,将当前楼层可出的使用者所有送出。我将电梯队列中未离开的首个请求做为主请求,在从当前楼层前去“取人”以及将该人送到目标楼层的过程当中,每一层都判断是否要出人是否要进人;在到达主请求楼层时,电梯不当即换向,先遍历使用者队列,将使用者中目标楼层同向的使用者都送走后,再调头。框架

此次做业的程序退出方式难度较大。基于个人设计,电梯从请求队列中取人不遵循FIFO策略,null不知足任何“捎带条件”所以将null传入使用者队列须要特判,另外一方面,若是null传入了使用者队列,电梯完成任务时,每一次遍历使用者的目标楼层时,都要添加使用者不是null这一个条件,使代码十分不美观。所以,在此次设计中我在请求队列类中添加End标记,若是请求队列仅有null一个元素(null不传入电梯,其他需求都会以非捎带或捎带方式进入电梯),end标记为1,当电梯使用者队列为空而且电梯调度器为End状态时,电梯结束运行。ide

(三)第三次做业设计方案

  • 模型:生产者消费者模型
  • 四个线程:输入(生产者)、三部电梯(消费者、生产者)
  • 共享对象:总请求队列
  • 退出模式:两个队列,一个队列为总请求队列、一个队列为换乘标记队列;总请求队列仅有null且换乘标记队列为空时总调度类end标记为1

第三次做业中电梯增长了限乘人数,限到楼层,三部电梯间换乘。在这个需求下,三部电梯在消费者角色以外,若有使用该电梯的乘客须要换乘,那么这部电梯又承担了生产者角色,将乘客放回总请求队列。工具

三部电梯的捎带问题仍在调度类getRequest方法中实现,本次做业中向getRequest方法传入参数更复杂,从总请求队列中向电梯使用者加人受电梯承载力的限制。遍历总请求队列时,优先选择这部电梯能直接送到最终目的地的请求,若是须要,应从换乘队列中删掉这我的;若是电梯尚可载人,再将须要换乘的请求加入电梯使用者队列,并将换乘请求加入换乘者队列。性能

电梯完成任务方式相似于做业2,须要注意的细节是保证每一层电梯要先下后上,避免超载。学习

线程结束的方式中,调度类End新增条件,换乘队列为空,避免人员还没有送达,电梯结束运行。测试

2、程序结构分析

(一)第一次做业

一、规模分析

Elevator类:

75行代码优化

属性:

private OpList elOp;   //共享请求队列
private int floor = 1;  //当前楼层
private static final int door = 250;//开关门时间
private static final int go = 500;//移动时间

方法:

public Elevator(OpList op) {} public void Op(PersonRequest pr) {}//电梯完成请求,20行 private void printOpen(int floor) {}//封装开门输出
 private void printClose(int floor) {}//封装关门输出
 private void printIn(int floor,int id) {}//封装进人输出
 private void printOut(int floor,int id) {}//封装出人输出
 public void run() {}

OPLIST类:

40行代码

属性:

private ArrayList<PersonRequest> wait;//请求队列
private static int limit = 1;

方法:

public OpList() public synchronized PersonRequest getRequest() //取请求,10行
 public synchronized void setRequest(PersonRequest newReq)//增长请求,10行

PRODUCER类:

30行代码

属性:

private OpList rawInput;//共享请求队列
private ElevatorInput elevatorInput = new ElevatorInput(System.in);

方法:

public Producer(OpList raw) @Override public void run() //往请求队列中加入请求

二、OO性能度量

第一次做业是生产消费模型的基础实现,所以代码复杂度低,耦合度低

三、类图

十分基础的架构,Elevator类是消费者,Producer类是生产者,共享对象OPList类。Main类中调用两个线程。

四、UML时序图

五、设计检查

  • 单一责任原则(SRP):输入线程仅负责向共享对象中添加需求、电梯线程仅对使用者队列中的需求依次知足。知足SRP
  • 开放性原则(OCP):第一次做业中不存在电梯调度策略问题,不存在电梯运行限制问题。个人设计经过对Elevator类的operate方法进行改进,并对addRequest类传入电梯运行参数,能够实现调度捎带功能。可扩展性良好。
  • 里氏替换原则(LSP):本次做业中,不存在继承关系。
  • 接口分离原则(ISP):本次做业中没有使用接口

(二)第二次做业

一、规模分析

 Elevator类

230行

属性:

private ArrayList<Person> users;//使用者队列
 private int floor = 1;//电梯所在楼层
 private Request eleOp;//电梯当前主请求
 private RequestList rl;//共享对象,请求队列
 private static final int move = 400;//电梯移动时间
 private static final int door = 200;//电梯开关门时间

方法:

public Elevator(RequestList re) {} private void printOpen(int floor){} private void printClose(int floor) {} private void printArrive(int floor) {} private int out(int floor,int mark1) {}//在特定楼层状况,25行
 private int operateDown(int from,int to,int count) {}//电梯下行过程处理,30行
 private int operateUp(int from,int to,int count) {}//电梯上行过程处理,30行
 private void operate(Person per,int from,int to,int choice,int count) {}//电梯完成主请求,57行
 public void run() {}//60行

Input类

40行

属性

private RequestList rawInput;// 共享对象
 private ElevatorInput elevatorInput = new ElevatorInput(System.in);

方法:

public Input(RequestList req) {} @Override public void run() {}

RequestList类:

114行

属性

private ArrayList<Person> wait;// 请求队列
private int end = 0;//结束标记为
private int in = 0;//区别第一次取人标记

方法

public RequestList() {} private void printOpen(int floor) {} public synchronized int getRequest(ArrayList<Person> ele,int floor,int des,int marker) {}//根据电梯状态从请求队列中取出请求,60行
 private void printIn(Person per,int floor) {} public synchronized void setRequest(Person newReq) {}//从输入端向请求队列中增长请求,5行
 public boolean empty() {}//判断当前请求队列是否为空或仅含有null
 public int getEnd() {}//判断输入线程是否结束输入(传入null)

Request类:封装请求

45行

属性

private int from;//请求中出发楼层
 private int to;//请求中到达楼层
 private String status = new String("");//请求中方向

方法

public Request(int from,int to) {}//获得请求方向信息
    public boolean carry(Person per) {}//判断是否人的请求同向可携带
    public int getFrom() {} public int getTo() {} public String getStatus() {} public void reset(int newFrom,int newTo) {}//请求重置

Person类:

25行

对PersonRequest进行封装

在PersonRequest基础上增长属性,方便捎带与调度

private boolean state;//是否进入电梯
private boolean out;//是否出电梯

二、oo性能度量

Elevator类中三个operate方法耦合度高,缘由是三个方法实际是电梯完成任务上行、下行、送到目标楼层全过程的分割,operateUp与operateDown都是operate中的一部分,而run方法中operate又是重要的部分。operateUp与operateDown两个方法设计不够巧妙,内容大多为对称映射关系。run方法复杂度高,耦合度高,主要由于run方法中为了实现优化,在实现取request,完成request的基础操做外,用循环遍历电梯使用者队列,调用operate方法,尽量多的将同向运行的乘客送出,这就致使个人run方法写的庞大不美观。

RequestList类的getRequest方法也是复杂度高,耦合度高的方法。耦合度高是由于电梯的调度在个人设计中是以这个方法为基础,经过电梯类中每一层都调用这个getRequest方法,经过返回参数,判断该层是否能够捎带进人,所以在elevator类中重复调用这个方法。其复杂度高的缘由在于,在这个方法中,嵌套了4层if else判断,并在最外层if else的每一种状况下都对请求队列进行遍历。

三、类图

 Person类封装了PersonRequest,Request类对人的请求与电梯运行任务作了统一。Elevator类是消费者,与Input类共享RequestList对象。RequestList类中的请求队列以及Elevator类中的使用者队列都是以Person为单位的ArrayList,Elevator类与PersonRequest类中,对于电梯当前运行状况以及等待着请求用Request类封装,判断是否能够捎带。

四、UML时序图

五、设计检查

  • 单一责任原则(SRP):PersonRequest类负责在具体楼层,针对电梯某一状态,点对点为电梯使用者队列增长人。电梯类的run方法中,同时实现取请求,完成主请求,顺路送人、捎带判断等任务,较为负责,安全性差。
  • 开放性原则(OCP):此次电梯的设计可扩展性较好。对于单个电梯的执行主请求,处理顺路送人,捎带的设计,扩展到有运行条件限制的电梯上也可适用。getRequest方法,只须要增长传入参数,也能够知足有运行条件限制的要求。
  • 里氏替换原则(LSP):本次做业中,Person类封装PersonRequest,变相继承Personrequest,子类能够替代父类。知足LSP
  • 接口分离原则(ISP):本次做业中没有使用接口

(三)第三次做业

一、规模分析

Elevator类

384行

属性

private static final int door = 200;//关门时间
private final int capacity;//承载力
private final  int move;//移动时间
private final String name;//电梯名称
private final ArrayList<Integer> floors = new ArrayList<Integer>();//可达楼层
private ArrayList<Person> users = new ArrayList<Person>();//使用者
private final ArrayList<Integer> setFloors = new ArrayList<Integer>();//与其余电梯交叉楼层
private int floor = 1;//当前所在楼层
private Request eleRep;//电梯当前主要请求
private Channel ch;//共享对象
private int first;//标志接过第一我的

方法:

goUP,goDOWN,operate,run仅在第二次做业基础上稍加改动

public Elevator(String str,Channel channel) {} public int scanFloor(int floor) {}//判断某楼层是否在可达楼层中
 public void addFirst() {} public boolean isFull() {} public int getNearest(int oto,int afrom) {}//换乘目标楼层,20行
 public int getFirst() {} public String Name() {} private void printOpen(int flo) {} private void printClose(int flo) {} private void printArrive(int flo) { } private int out(int mark1) {} private boolean findFloor(int floo) {} public int goUp(int to) {} public int goDown(int to) {} private void operate(Person per,int des,int choice) {} private boolean isIn(Person person) {} public void run() {}

Channel类:

170行

属性:

private ArrayList<Person> wait;  //请求队列
    private ArrayList<Elevator> eles;  //电梯线程池
    private ArrayList<Integer> ag;    //须要换乘者标记队列
    private boolean end = false; private boolean end1 = false; private int carry = 0;

方法:

public Channel() {} private int fin(int id) {} //遍历换乘者队列
public synchronized void addRequest(Person per) {}//从输入端增长请求
public boolean isEnd() {}  //判断请求队列为空且输入结束且换乘队列为空
private void printOpen(int floor, String str) {} private void printIn(Person per, int floor, String str) {} public synchronized int getRequest(Elevator ele, ArrayList<Person> users,int floor, int des, int marker, int ca) {}  //向电梯使用者队列投放请求 ,90行
public boolean getEnd() {} public void startEle() {}

Person类

属性:

private final int id; private boolean stateIn; //电梯内判断位
private boolean stateOut; //出电梯判断位
private final int ofrom; //原始请求出发楼层
private final int oto; //原始请求到达楼层
private int afrom;  //实际出发楼层 
private int ato; //实际到达楼层
private ArrayList<String> inList; //搭乘过电梯标记队列

方法:

public Person(PersonRequest pr) {} public boolean getInS() {} public boolean getOutS() {} public void setStateIn(boolean bo) {} public void setStateOut(boolean bo) {} public boolean canIn(Elevator ele) {}  //判断是否可仅当前电梯
public void setTo(int to) {} public void setaFrom(int from) {} public int getaFrom() {} public void setOut(Elevator ele) {}  //根据当前所进电梯判断实际到达楼层为多少
public boolean oCanOut(Elevator ele) {} //判断当前电梯是否能够直接送到原始目标楼层
public int gi() {} public int getaTo() {} public int getoTo() {} public void addIn(String str) {}

Input类

同第二次做业

Request类

同第二次做业

二、oo性能度量

getRequest方法复杂度高,嵌套4层if else判断,且每个条件分支下都有遍历。对于请求队列与换乘者队列,存在遍历后删除某些项,重建队列的操做。耦合度高,由于该方法在Elevator类的operate方法中被调用频率太高。

Elevator类的goUP,goDOWN,operate方法耦合度高的缘由同第二次做业。run方法的复杂度,耦合度圈复杂度高,缘由也同第二次做业。

Person类CanIn复杂度高由于其内部有4层if嵌套,且最后一次嵌套中存在对使用过电梯队列的遍历。

三、类图

 Channel线程中的线程池中建立三个Elevator类,Elevator类与Input类共享Channel中的waitList对象,三个Elevator类共享一个Channel总调度器。

Person类与Request类是不可再拆分的原子类。

四、UML时序图

五、设计检查

  • 单一责任原则(SRP):Elevator类存在安全隐患,run方法的责任过多,不只须要从电梯使用者队列中取出请求,还须要知足与前往请求发出者的方向一致的电梯使用者的送达需求。这个设计使run内部users的增长,删除操做混乱,致使bug出现。
  • 开放性原则(OCP):代码可扩展性通常,由于最核心的addRequest方法已经因为依赖当前电梯信息过多,受到了很大限制。
  • 里氏替换原则(LSP):本次做业中,Person类封装PersonRequest,变相继承Personrequest,子类能够替代父类。知足LSP
  • 接口分离原则(ISP):本次做业中没有使用接口

3、程序bug分析

第一次做业中,公测与互测未发现bug,可是在本身写代码的时候,因为不熟悉锁机制,对OPList方法加物理锁后,未及时notifyAll,出现了死锁。

第二次做业中,刚开始没有处理好Elevator线程终止判断方案,致使程序终止不了,最终RTLE报错。最后发现bug点在于个人Elevator判断终止条件为usersList仅有null一个元素,可是PersonRequestList在一些状况下未将null传入Elevator类。这个问题不算是线程安全问题,主要是设计不够完善。最终,我转变思路,对共享对象类设计End标记位,向Elevator类传递结束信号,保证程序正常结束。

第三次做业中,我仅对addRequest与getRequest两个共享对象中的方法上物理锁,其他对象,均为单一线程享有。这个设计保障了线程安全性。可是单一Elevator线程内部,我对于电梯使用者队列的“取与删除”的安全性设计存在缺陷。致使我在本地测试中出现了“乘客电梯中失踪“,乘客尚未输入“IN”,就被从电梯使用者队列中删去的问题。前者我在本地测试中勉强补救,后者在本地测试中未发现,致使强测中丢失了两个点。

总的来讲,个人设计尽可能避免了“共享”,上锁局限在对方法上锁,必定程度上保证了线程安全性。然而,抛开线程安全性,个人设计中,在电梯类调度优化时,考虑并不全面,对于电梯的使用者队列的维护不够完善,致使最终电梯使用者队列的增长与删除出现了冲突,出现了bug。

4、发现别人的bug策略

在第一次做业互测阶段,我阅读别人的代码,发现你们实现的思路框架基本一致,应该不存在设计上的bug。

第二次做业互测阶段,因为未搭建评测机,我只有尝试输入本身本地测试的数据,对同屋的人代码随机测试,无奈没有发现bug。

第三次做业互测阶段,已经搭建好评测机,我经过data.py生成随机数据,通过TestClass文件解析时间戳,将输入流定时输入到同屋小伙伴的代码中,并将输出结构递交给check.py。check.py对获得的输出数据按照指导书中的正确性要求进行检查,若是知足所有正确性要求,则循环回到data .py生成随机数据这一步;不然,循环中止,cmd界面显示报错信息。循环生成随机数据测试法亲测有效。

与第一单元互测阶段发现bug相比,第二单元发现别人程序bug难度更大。想要有效发现别人程序的bug,必定要创建起一套完整的,成体系的找bug工程。

首先,第二单元的定时输入是咱们没法经过idea输入端手动完成的,咱们必须经过.sh文件以及时间戳解析文件,将带时间戳的输入信息保存到文本文档,输入其余人的程序中进行测试。

其次,第一单元的前两次做业,把目标放在检查别人对于输入是否合法的判断是否全面,死抓WRONG FORMAT错误就能够狼到一些人,可是第二单元,输入流是官配的,狼人重点放在了对于合法的输入,其余人的代码输出的正确性问题。

第三,第二单元中,输出正确性判断难度较大。第一单元中,经过MATLAB,计算求导结果尚能够判断输出结果是否正确,到了第二单元,尤为是是第三次做业,三部信息交叉输出,输出正确性断定条件较多,尤为是在输出数据较多时,肉眼几乎没法判断输出是否正确。所以,第二单元判断别的输出正确性必定须要正确性解析代码。

第四,第二单元中,因为多线程输出的不肯定性,一些本地输出错误结果的测试样例,提交到评测平台时,获得了正确的输出结果。这种状况在第一单元输出结果肯定的状况下是万不会发生的。多线程不安全设计形成的bug的不可复现性要求咱们提升测试数据的投入量。

 

5、心得体会

一、线程安全

这三次做业中,为了保障线程安全性,我尽可能作到减小共享,防止冲突的发生。对于共享对象的上锁,我局限于共享对象类的方法上锁,保障一个线程调用该方法时,一个共享对象实例所有被锁住。个人实现方法虽然说提升了线程安全性,可是灵活性不够,这暴露了我对于Java的线程安全以及锁机制的理解不够透彻。

通过研讨课同窗的分享以及互测中阅读其余同窗代码,我认为在后续多线程学习中我应该主动尝试对共享类的某个属性上锁,学习并使用可重入锁,使用atomic包的原子性工具。用更丰富,更灵活的手段保障线程安全。

二、设计原则

相比于第一单元每一次做业都重构的情况,本单元我格外注意代码的可扩展性,第一次做业搭建好生产者消费者架构,为后两次做业提供基础,第二次做业的电梯调度策略可直接迁移到第三次做业中单个电梯线程的调度设计中。能够说,三次做业的设计思路是一脉相承的。

本单元我在单一责任原则上作得很不完善。第二次做业开始,电梯调度策略的实现所有装入run方法,使run方法冗长难看不说,还致使了电梯调度过程当中,使用者队列的安全性得不到维护,最终潜藏的隐患遗留到了第三次做业中集中爆发,强测挂点。这个问题的根源是我没有对电梯调度问题整理清思路,将“调度”问题剥离出几个“子过程”,分割实现,而是一股脑丢进run方法,套在多层循环中实现。在第三次做业中,原本想在Elevator类以外设计operate方法,在Elevator中直接调用,让代码更加美观,惋惜因为我设计电梯处理请求以及调度问题的思路不够清晰,没有实现operate方法从电梯类中分离。

相关文章
相关标签/搜索