一、作业设计策略

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

二、程序结构分析

(一)第一次作业

1、规模分析

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) @Overridepublic void run() //往请求队列中加入请求

2、OO性能度量

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

3、类图

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

4、UML时序图

5、设计检查

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

(二)第二次作业

1、规模分析

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) {}@Overridepublic 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() {}//判断当前请求队列是否为空或仅含有nullpublic 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;//是否出电梯

2、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的每一种情况下都对请求队列进行遍历。

3、类图

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

4、UML时序图

5、设计检查

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

(三)第三次作业

1、规模分析

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类

同第二次作业

2、oo性能度量

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

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

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

3、类图

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

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

4、UML时序图

5、设计检查

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

三、程序bug分析

第一次作业中,公测与互测未发现bug,但是在自己写代码的时候,由于不熟悉锁机制,对OPList方法加物理锁后,未及时notifyAll,出现了死锁。

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

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

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

四、发现别人的bug策略

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

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

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

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

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

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

第三,第二单元中,输出正确性判断难度较大。第一单元中,通过MATLAB,计算求导结果尚可以判断输出结果是否正确,到了第二单元,尤其是是第三次作业,三部信息交叉输出,输出正确性判定条件较多,尤其是在输出数据较多时,肉眼几乎无法判断输出是否正确。因此,第二单元判断别的输出正确性一定需要正确性解析代码。

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

五、心得体会

1、线程安全

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

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

2、设计原则

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

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

转载于:https://www.cnblogs.com/jessyswing/p/10743628.html

BUAA OO第二单元作业总结相关推荐

  1. 【OO学习】OO第二单元作业总结

    OO第二单元作业总结 在第二单元作业中,我们通过多线程的手段实现了电梯调度,前两次作业是单电梯调度,第三次作业是多电梯调度.这个单元中的性能分要求是完成所有请求的时间最短,因此在简单实现电梯调度的基础 ...

  2. BUAA OO 第二单元总结

    OO 第二单元总结 本文为OO 第二单元电梯作业总结,本单元主要是掌握多线程和线程安全.三次作业总的架构类似,可分为输入线程.调度线程.电梯线程.三次作业的增量迭代如下 第五次作业 六部电梯 第六次作 ...

  3. OO第二单元作业小结

    总结性博客作业 第一次作业 (1)从多线程的协同和同步控制方面,分析和总结自己三次作业的设计策略. 第一次作业为单电梯傻瓜调度,可以采用生产者--消费者模型,是一个有一个生产者(标准输入电梯请求),一 ...

  4. BUAA OO 第二单元 电梯

    前言 第二单元的主要任务是模拟电梯,主要涉及调度.电梯内部运行策略.换乘等,培养多线程编程的能力. 第一次作业 第一次作业的要求较为简单,模拟6部电梯的运作以及调度即可. 分析 笔者参考了上机所给的代 ...

  5. OO第二单元作业分析

    前言 这一单元关于线程安全的作业结束了,在助教提供的接口的帮助以及老师提供的设计模型的指导下,这三次作业还是相对轻松地完成了,中间也没有出现什么bug,可能就是因为简单的逻辑不容易出错吧,可惜两次都由 ...

  6. OO第二单元作业总结

    一:设计策略 第一次作业:第一次是单电梯傻瓜调度策略,因此我把调度器当作共享资源对象,有一个put和一个get方法,因为只有一个电梯,并且单次取出和投放一个请求,因此只需要同步控制一下这两个方法是互斥 ...

  7. 多线程编程初探——OO第二单元作业回顾

    一.作业设计策略 1)执行FAFS策略的单部电梯 ​ 由于对多线程不是很了解,于是采用了理论课上介绍的生产者消费者模型作为设计模板(也是很多同学一开始的做法):将请求队列作为共享对象(托盘),名为In ...

  8. 面向对象课程第二单元作业总结

    面向对象课程第二单元作业总结 前言 电梯系列作业分为三个阶段,逐步深入实现越来越复杂的电梯运行状态模拟. 第一阶段实现单部多线程傻瓜调度(FAFS)电梯的模拟: 第二阶段实现单部多线程可捎带调度(AL ...

  9. OO第一单元作业总结

    OO第一单元作业总结 第一次作业 结构 OO第一次作业是简单的多项式求导,在这次多项式求导中,仅包含了幂函数和带符号整数的一些简单求导运算.在经过自己的一些简单分析后,我认为第一次作业最重要的部分是判 ...

最新文章

  1. C++学习路线(最全资源整合)
  2. MIT自然语言处理第五讲:最大熵和对数线性模型
  3. windows和Linux内存的对齐方式
  4. 多比Web 3D展示(3D机房/3D监控)中间件多比Web 3D展示(3D机房/3D监控)中间件免费下载购买地址...
  5. 离线部署CDH5.16.1及各种坑
  6. hdu1269迷宫城堡(判断有向图是否是一个强连通图)
  7. 运行tuxedo自带例子simpapp,测试tuxedo安装
  8. java实现gps定位_GPS定位数据的提取与存储系统的设计
  9. 【密码学】一文读懂ZUC算法
  10. 旧iPhone 通讯录数据如何完整迁移到新iPhone13?
  11. swiper控制页面切换
  12. unity3d摄像机的透视有些夸张怎么办?
  13. c语言中如何识别空格键,在C++中如何判断“空格”键输入?
  14. Broccoli Tree Creator 使用说明 3_3、Girth Transform Node (周长变换节点)
  15. 组合数据类型练习,英文词频统计实例
  16. java 获取token
  17. tcp实时传输kafka数据_将物联网数据和MQTT消息流式传输到Apache Kafka
  18. 在数据库EMIS1中;把日志文件初始大小修改为10MB,增长量为每次增长5MB,最大大小保持不变。
  19. JS中操作<select>标签选的值
  20. C语言 利用高斯消元求解方程组

热门文章

  1. IBM WebSphere Application Server与IBM Java V8官方帮助文档资源汇总
  2. android---多点触控
  3. “闪婚”的代价:亿晶光电控股权陷“罗生门”
  4. 唉,江湖阅历不够,在接洽一个S5PV210 wince 项目的时候被一个贱人给骗了
  5. 汽车UDS诊断详解及Vector相关工具链使用说明——2.1.3 初步了解CDD(以10服务为例)
  6. 简单的解释下什么是CNAME?
  7. Cat_Lee 的博客开张了。 欢迎各位 有空常来坐坐。
  8. VUE 文字转语音播放的实现示例(亲测好用)
  9. Android学习--RecyclerView的使用
  10. python文件处理练习14