BUAA_OO_Unit2 线程安全设计总结

BUAA_OO_Unit2 线程安全设计总结

一、综述

​ 面向对象课程的第二单元的主题是利用多线程设计来模拟电梯的调度和运行。这一单元主要考察我们对并发和多线程的理解,基于共享、交互的面向并发和协调的层次设计能力,以及最重要的维护并发行为的安全设计能力。

​ 本单元共三次作业,每次作业都是在上一次作业的基础上做一些更新与迭代,即增加了一些要求使得多线程之间的数据共享更加复杂,对多线程以及生产者-消费者,单例模式等理解和运用进行了有效训练和考察。

二、作业分析

1. Homework 5

1.1 UML类图

1.2 时序图

1.3 分析

1.3.1 架构设计

​ 本次作业共InputThreadScheduleElevator三个线程类,并采用二级托盘的模式(用生产者-消费者模型)实现,InputThreadScheduleScheduleElevator之间的数据交互。

​ 调度线程由输入线程和队列中请求数量决定是否结束线程,电梯线程由调度线程、队列中请求数量和是否存在主请求决定是否结束线程。

1.3.2 调度和运行策略

​ 由于第五次作业只有五个楼座各有一部电梯,并且每部电梯的运行不相互交互和影响,所以实际上就是根据请求的属性将请求分配到每个电梯的等待队列中去。

​ 调度策略上基于ALS设计了一种比较容易实现的策略:

  1. 如果电梯中没有乘客并且等待队列没有人,则线程wait,当线程被唤醒后,如果等待队列依旧没有人,则将主请求设置为null,并且结束线程。
  2. 主请求优先,如果主请求不在电梯上,则直到接到主请求,电梯的目标楼层是主请求所在楼层;当接到主请求后,电梯的目标楼层是主请求的目标楼层;
  3. 当主请求下电梯后,电梯的运行方向改为0,然后重新选取新的主请求;
  4. 在捎带策略上,分两种情况,接主请求的过程中,捎带的人需要在接到主请求前就能下电梯(包括目的地和主请求所在楼层相同的情况);在送主请求的过程中,捎带的人只需要方向和电梯运行方向一致就进行捎带处理。

2. Homework 6

2.1 UML类图

2.2 时序图

2.3 分析

2.3.1 架构设计

​ 第六次作业在第五次作业的基础上加入了横向电梯,增加电梯的功能要求。

​ 第六次作业在本质上和第五次作业并无太大的区别,依旧采用InputThreadScheduleElevator三个线程类,并采用二级托盘的模式(用生产者-消费者模型)实现,InputThreadScheduleScheduleElevator之间的数据交互。只是在Schedule线程中根据请求的种类(增加电梯请求和乘客请求),遇到增加电梯请求时开一个新的线程。

2.3.2 调度和运行策略

​ 由于多部电梯的出现,需要给可以完成相同任务的电梯进行分配任务,笔者采用自由竞争的策略。不过由于ALS的特殊性(主请求的存在),笔者设计了一个缓冲区,当电梯选中主请求后,主请求就进入了该电梯的缓冲区,这样就保证了主请求一定会被该电梯接走,使得其不会空跑。在满足有主请求之后,对于其他请求就自由竞争。

​ 纵向电梯的调度策略和第五次作业没有区别,横向电梯的调度策略采取和纵向电梯类似的策略。由于从一个楼座到另一个楼座总有两个方向,并且总有一个方向经过的路程要更小,所以去路程较小的方向运行。(实际上这里有个小问题,就是这种策略对于捎带的人数可能会变少)。

3. Homework 7

3.1 UML类图

3.2 时序图

3.3 分析

3.3.1 架构设计

​ 第七次作业在第六次作业上增加了换乘的功能。

​ 第七次作业依旧采用InputThreadScheduleElevator三个线程类,并采用二级托盘的模式(用生产者-消费者模型)实现,InputThreadScheduleScheduleElevator之间的数据交互。相比第六次作业,将Schedule线程中根据请求的种类(增加电梯请求和乘客请求),遇到增加电梯请求时开一个新的线程的功能提前到InputThread中。当遇到一个乘客请求时,Count++;

​ 同时建立一个map用来存储每层楼横向电梯的可达楼座信息M,用来帮助换乘。笔者将乘客请求封装成Passeage类,包含了turSear、tarSeat、termSeat、turFloor、tarFloor、termFloor六个属性,tur前缀代表乘客所在位置,tar前缀代表乘客下一步目的地,term前缀代表乘客最终目的地。当乘客的所在楼座和最终目标楼座不同时,意味着需要判断是否换乘;换乘时改变乘客下一步目的地,然后根据所在位置和下一步目的地对乘客进行分配。当乘客到达目的地时,再修改其所在位置.。如果乘客到达最终目的地,则Count--;否则返回托盘,由调度线程重新分配。

3.3.2 调度和运行策略

​ 第七次作业和第六次作业相比,每部电梯的各种属性都不相同,所以继续采取自由竞争的方法,笔者觉得可以充分使得电梯使用效率提高。同时,由于横向电梯有了停靠楼座的限制条件,需要给横向电梯选取主请求和捎带的地方增加限制,保证不会乘客不会进错电梯。

4. 同步块与锁设计

​ 三次作业都采取了synchronized关键字以及对象锁。当一个共享对象涉及到同时读写时进行上锁。由于共享对象的结构单一性,采取对共享对象内部的一些方法进行上锁,只有在电梯的上下客方法中才加上了额外的synchronized同步块,因为这里涉及到了对于共享对象中的一个数组进行遍历与修改操作。

三、BUG分析

1. Bug

1.1 分析Bug策略

​ 第二单元的作业涉及多线程,导致很多Bug难以复现,笔者也是花了很多时间分析了自己代码的bug。笔者将最常见的Bug分为两类,一类是逻辑Bug,一个就是轮询问题。

1.2 三次作业的Bug

​ Homework 5: 本次作业有一个点出现了CPU占用过高的Bug,也就是某个线程没有及时Wait导致的问题。笔者花了大量的时间对线程的等待逻辑进行了检查,最后也没有发现什么问题。最后还是通过对强测数据的观察发现是笔者捎带策略的逻辑错误,导致电梯接满6人后导致主请求上不了电梯,最后导致电梯一直在查询请求队列。笔者认为这本质上是一个逻辑上的Bug。

​ Homework 6:本次作业在强测和互测中并没有出现什么Bug,笔者也自己构造了许多数据也并没有出现问题。

​ Homework 7:本次作业有一个很严重的轮询问题,导致强测错了一个点,在互测阶段被同一房间的同学找到了漏洞。这个Bug是在横向电梯选取主请求中出现的,由于存在可停靠楼座的限制,在选取主请求时不仅要满足队列为空时Wait,同时队列不为空且电梯不能满足乘客停靠需求时也要Wait,由于笔者漏掉了第二个条件,导致横向电梯线程CPU_TIME_EXCEED

2. Debug

​ 在找他人的Bug时也同样分为寻找两种Bug,先判断电梯运行逻辑是否有问题,比如三次作业中是否接错人,第三次作业是否换乘出现问题等。然后再判断线程是否出现轮询问题,比如没有及时Wait或是没有结束线程等等。在检测电梯运行逻辑的时候,笔者是采用自动评测的方法进行的。

四、感想和心得

​ 第二单元的三次作业在整体上来看要比第一单元三次作业处理起来要显得更加得心应手,这可能是因为经过一段时间的训练,个人对于Java语言的掌握和使用有了一定的提高。虽然第二单元作业相对于第一单元作业出现了更多的Bug,但是笔者收获颇多。无论是从作业还是课上实验,亦或是理论课程,都提供了很多资源帮助学习多线程的安全维护。

​ 除了多线程编程,本单元作业还涉及到了生产者-消费者模型,流水线模式等模型,提供了多种新的解决问题的新手段。本次作业虽然没有用到读写锁,但是也阅读了使用读写锁同学的代码,了解了读写锁强大的功能,希望下次编程能够使用到。

posted @ 2022-05-03 10:44  薛定谔的猫SC  阅读(40)  评论(0编辑  收藏  举报