其他分享
首页 > 其他分享> > 『BUAA-OO-Unit2-Summary』

『BUAA-OO-Unit2-Summary』

作者:互联网

『BUAA-OO-Unit2-Summary』

Homework5

0. 写在前面

由于没有看清DDL,导致本次作业卡点提交未能成功通过。(悲)

作为多线程编程的初学者,我认为本次作业的难点有三:

  1. wait()/notify()的使用;
  2. 共享对象类的构造;
  3. 调度策略的代码实现;

接下来,我将对本次作业的实现过程进行一个大致的复现,并对以上难点着重展开讲述。

1. 重点关注:同步块/共享对象/线程安全类

关于多线程程序设计,在拿到题面/了解客户需求后,设计架构时要提前想好以下几个问题:

对于第五次作业来说,我们需要以下几个线程类

如果分别构造InputThreadDispatcher两个线程类的话,那么还需要实现一个供二者进行信息交互的共享对象类。但是二者均属于单例模式类,也就是说,自始至终只有两个线程会来访问这个共享对象,并且一个只会写,一个只会读。这样的设计显然十分不合理,因此我将InputThreadDispatcher合并为一个线程类并命名为Dispatcher,其功能为从标准输入中获得乘客请求并将其分配到对应的队列中(即二者功能合并)。

至于共享对象,就十分明显了:Dispatcher需要将乘客信息写入一个队列,而Elevator类的五个实例(5 elevators)则需要从该队列中选择自己的任务来工作,并在任务完毕后将该任务从队列中删除。因此,共享对象就是各楼座的等候队列,创建共享对象类WaitQueueWaitQueue的内部结构,我选择了同时维护一个ArrayList<>与一个ArrayList<ArrayList<>>,维护ArrayList<ArrayList<>>的目的是快速返回某楼层的请求信息,而ArrayList<>则起到了保留请求到达先后顺序的作用。采用这样看起来有些复杂的结构实际上有一个好处:明面上提示了设计者需要使用同步块。尤其是对于当时刚刚接触多线程的我来说,其实对共享对象读写的保护意识并没有那么强烈,但是在addremove WaitQueue中请求时总是要同时对两个数据结构进行操作的这个事实总是在不断地提示着我读写方法的非原子性,共享对象的读写方法必须加synchronized同步块保护才能在线程中放心大胆地任意使用。

线程共享对象的关系?Dispatcher线程对WaitQueue进行写入操作(W),而Elevator既要从WaitQueue中读取数据,还要在任务完成后将数据清除(RW)

这几个基本的问题思考清楚以后,我们便可以开始动手实现了!

2. 调度策略

关于调度策略,虽然学长与助教强烈推荐了LOOK算法,但我还是倔强地使用了ALS策略。

说实话,其实ALS策略并不是很好写。主要概念只有两个:主任务与捎带任务。实现分四步走,思路如下:

getMainMission();
move();
getNextMovingDir();
outAndIn();

至此,调度策略讲解完结。

3. 架构模式(类图/SD图)

4. Bug修复

本次作业中测未能成功通过,CTLE的原因即为没有完全搞懂wait()/notifyAll()的实现机制,导致共享队列为空时反复轮询。对此漏洞进行修复后,在第六次作业强测中又一次出现了CTLE的问题。这个问题一会儿再细说。

并且第一次堆砌的自以为实现了ALS策略的ShitMountain复杂度太高,应该也是导致超时的重要原因。

Homework6

0. 写在前面

本次迭代要求新增两大亮点:

1. 重点关注:线程安全/wait()/notifyAll() [详见Bug修复板块]

本次作业难度真的不是很大。但是由于没能成功参与第五次作业的互测,导致第六次作业强测中出现了本该在上一次作业中解决的历史遗留问题。

首先,先来谈一谈环形电梯的实现:

废话了这么多,第六次作业中我的环形电梯采用了运行方向可变的最优调度模式。由于实现方法与纵向电梯较为类似,只是计算最优路径时增加了一些取模运算,这里不再赘述。

而多台电梯的问题,我还真的思考了很久没有想到一个最佳的调度方案。后来无意之中提交后奇迹般通过后才意识到让共享同一队列的电梯间自由竞争居然也是一种解决方式。

2. 思考:HiddenDispatcher(隐式任务分配)

废话少说,直奔主题:

前一段时间我一直在思考这个问题,电梯线程间的信息是不能共享的,那么如何实现共享同一WaitQueue的线程间任务的最优分配呢?

e.g. 1-FROM-A-7-TO-A-8, 2-FROM-A-8-TO-A-10, 3-FROM-A-2-TO-A-1;其中,A座共有两台电梯,一台(1)停在6层,另一台(7)停在3层。

采用传统的自由竞争策略,必然会导致4号与7号电梯共同竞争上行的两个请求,7号电梯节节败退,白白跑到8层,一个请求都没有接到,而3号请求却始终无人问津的窘况。

为了破解这一难关,并且避免电梯线程间信息共享,就需要构建一个可以同时获取WaitQueue信息以及共享这一WaitQueue的全部电梯当前所在楼层及运行方向的Dispatcher。虽然听起来是一个好主意,让人叹为观止;但是听起来也有一点麻烦,让人望而却步。

但是转念一想,自由竞争方案中难道就不存在任务的分配器了吗?即使只考虑一个电梯线程,在进行主任务获取时,getMainMission方法在传入参数中仍然获得了当前电梯的所在楼层,getRequest()方法不仅获得了当前电梯的所在楼层,还获得了运行方向。而两个方法都是WaitQueue中的方法,也就是说我完全可以将这两个方法独立出来,形成一个MissionDispatcher线程帮助电梯确定主任务,以WaitQueuemainMission(Elevator)为共享对象,将二者关联起来;换句话说,这样的一个MissionDispatcher线程类如果只针对单一电梯线程进行任务分配,那么完全可以将MissionDispatcher的全部功能简化为method方法,归并入WaitQueue的一部分。

就这,我愿意称之为隐式任务分配(Dispatcher hidden in methods)。

3. 架构模式(类图/SD图)

4. Bug修复

两个漏洞:

  1. 第五次作业的残留问题:没有保护输出线程(不安全),需要套上synchronized语句块。(看了讨论区一位同学的分享,讲的很清楚,这里不重复)
  2. 关于wait()/notify()/synchronized的实现机制:
    • 我对这个问题一直有一些误解,以为被synchronized获得的锁在跳出语句块释放后,其他因为需要获得同一对象的锁而无法正常执行的synchronized()语句块不会被自动唤醒,需要人工notify()一下。实际上完全没必要,这是我的误解,只用使用wait()被迫人工等待的线程需要被人工唤醒。(你系的铃你解,系统系的铃系统自己解!)

Homework7

0. 写在前面

迭代起来极具挑战性(Physically & Mentally)的一次作业:

​ 加上了这两条要求后(主要是第二条),工作量几乎可以说是对先前代码的全面重构。

没错,我就是盯着这样一个支离破碎还漏液变色的电脑屏幕重构了10个小时。凭借着超人的意志以及强大的视力完成了这次作业的我真的很佩服我自己。

P.S. 知识方法类的感想与收获在前面两次作业的总结中已经介绍的差不多了。HW7将以介绍具体的设计思路与实现为主。

1. 迭代思路与难点

就是说因为乘客起点与终点并不一定在同座或同层,所以存在一个换乘的问题。如何进行换乘呢?

我们先不关心路径的算法,先思考这样一个问题:用什么样的数据结构来存储路径呢?

这里我们可以注意到,由于path[]数组与PersonRequest类现在是不可分割的,我们随时都可能要同时获得二者的信息,或者依赖二者合作获得我们想要的数据。

获得路径后,如何具体实现换乘操作呢?

换乘问题解决了。回到开始的那个问题:如何计算换乘路径呢?

这样,请求多样化的迭代就基本完成了。

最后的最后,由于换乘的存在,我们遭遇到了一个棘手的问题:

即使此时输入已经关闭,某电梯为空,也不代表该电梯线程一定结束。因为可能在其他电梯内的某乘客一会儿可能会乘坐该电梯中转。解决方案:构造一个Counter计数器,每输入一个新请求,调用incrementTotalRequests(),记录请求总数;每完成一个请求,调用incrementFinishedRequests(),记录完成请求数。在输入关闭后,判断两个static int变量是否相等,若相等,则各线程setEnd();若不等,则wait()。这样在避免反复轮询的同时,可以起到判断程序是否终止的作用。

2. 架构模式(类图/SD图)

P.S. 由于本次结构较为复杂,略去末尾setEnd()的信息传递。

3. Bug修复

忽略了一个小问题:

由于同层横向电梯的规格可能有所不同,导致横向换乘乘客的需求并非每台电梯均可满足。在设计载客逻辑时忽略了这一问题,导致某些乘客不小心坐上了错误的电梯,而无法在到达目的地时成功下车。

主要在以下三处进行了修改:

  1. 主任务的判断:电梯在选择主任务时必须保证有能力完后才能该任务,为了使等候队列分配给电梯正确的主任务,需要在getMainMissionFromFloorWaitQueue()函数中传入电梯的access信息。

    P.S. 由于对主任务选择逻辑进行修正后,即使在等候队列不为空的前提下,主任务获取仍有可能失败,因此在函数返回值为null的情况下,需添加wait()语句进行异常处理。

  2. 获取getInList:在获得可进入乘客名单时,也需要在getRequestInCurBlock函数中传入电梯的access信息。

  3. 增加一个新函数:以上两处改动中,在判断乘客是否可以乘坐当前电梯时,均需要获取乘客在横向电梯的换乘终点。因此,新增getNextBlock()函数获取这一信息。

Summary

标签:OO,请求,作业,BUAA,电梯,线程,Unit2,共享,WaitQueue
来源: https://www.cnblogs.com/augusto/p/16220261.html