301
社区成员
发帖
与我相关
我的任务
分享

架构模式角度来看,我在第二单元三次作业均采用的是生产者-消费者模型,主要便是围绕电梯问题中的生产者、消费者以及托盘进行建模:
生产者:InputThread线程
消费者:Elevator线程
托盘:共享资源RequestTable
不同于一般的生产者消费者模型,我在HW5中采用的其实是一个生产者,多个托盘以及多个消费者
在单个电梯的运行策略上,我采取了look策略,即电梯运动时,如果电梯内已经没有请求且运动方向上剩余楼层无请求则换向,若是请求表为空则进入stop状态,停在当前层,然后在访问请求表的方法中进入wait状态,等待请求加入或输入线程结束信号将其唤醒。
本次作业不涉及电梯的调度策略,因为输入已指定电梯接送乘客。
各类的功能
InputProcessor类:
输入处理线程 读取输入,把输入加入requestTable。在输入结束时,调用RequestTable中end方法。
RequestTable类:
全局请求表,共享对象,加synchronized保证线程安全 用Arraylist<PersonRequest>存储请求
Elevator类:
存储电梯状态,实现电梯线程
InputManager类:
存储全局变量,标记输入是否已结束
此次作业中只设置一个共享对象——RequestTable
保证只有InputProcessor类调用RequestTable中的方法时才notify,实现InputProcessor类对Elevator类的单向notify在设计上比避免死锁
RequestTable
所有读写总请求表方法均加sychronized锁,仅允许一个线程访问共享对象。 当addRequest方法被调用时会notifyAll,唤醒等待中的电梯线程。
当电梯调用noMoreThingsToDo方法发现请求表为空时则进入wait状态
无bug,强测95.9分


第六次作业中的架构设计基本延续第五次作业的生产者-消费者模型,最明显的区别就是增加waitTable托盘,这样的设计其实已经不是传统的生产者-消费者模型了,而是变成了具有二级托盘的生产者-消费者模型。InputProcessor将读入的乘客请求加入到共享资源waitTable中,然后由本次作业中新设计的调度器Scheduler类从waitQueue中取出请求再放入到不同的requestTable中。
设计原因:
本次作业新增reset请求,可能存在六个电梯都被reset时,输入线程收到请求但无法分配的情况,在第五次作业的架构上我想到很多处理的办法,但无一例外都需要大量的特判,我认为不是好的实现方法。因此改变架构以适应新增的reset请求。
只需要保证InputProcessor单向唤醒Schedule,Schedule单向唤醒Elevator即可避免死锁,所以设计共享资源方法时要想清楚是哪个线程会调用该方法,即可知道该方法能否添加notify语句。
各类的功能
InputProcessor类:
输入处理线程 读取输入,把输入加入requestTable。在输入结束时,调用RequestTable中end方法。
WaitTable类:
等待分配请求的表,存储无电梯可分配时读入的请求;电梯执行reset请求时未到达目的地的等待二次分配的请求。
Schedule类:
请求分配线程,我的分配逻辑是round-robin,第1个请求给第1个电梯,第2个请求给第2个电梯...第7个请求给第1个电梯,以此类推
RequestTable类:
全局请求表,共享对象,加synchronized保证线程安全 用Arraylist<PersonRequest>存储请求
Elevator类:
存储电梯状态,实现电梯线程
InputManager类:
存储全局变量,标记输入是否已结束
此次作业中设置两个个共享对象——RequestTable和WaitTable
保证只有InputProcessor类调用RequestTable中的方法时才notify,实现InputProcessor类对Elevator类的单向notify在设计上比避免死锁
RequestTable
所有读写总请求表方法均加sychronized锁,仅允许一个线程访问共享对象。 当addRequest方法被调用时会notifyAll,唤醒等待中的电梯线程。
当策略类调用noMoreRequestToHandle方法发现请求表为空时则进入wait状态
WaitTable
所有读写总请求表方法均加sychronized锁,仅允许一个线程访问共享对象。 当addRequest方法被调用时会notifyAll,唤醒等待中的策略类线程。
当电梯调用noMoreThingsToDo方法发现请求表为空时则进入wait状态
reset更新电梯容量后没有在上乘客时使用新的电梯容量判断是否超载


相较于第六次作业无架构层次的变化
具体实现
各类的功能
InputProcessor类:
输入处理线程 读取输入,把输入加入requestTable。在输入结束时,调用RequestTable中end方法。
WaitTable类:
等待分配请求的表,存储无电梯可分配时读入的请求;电梯执行reset请求时未到达目的地的等待二次分配的请求。
Schedule类:
请求分配线程,我的分配逻辑是round-robin,第1个请求给第1个电梯,第2个请求给第2个电梯...第7个请求给第1个电梯,以此类推
RequestTable类:
全局请求表,共享对象,加synchronized保证线程安全 用Arraylist<PersonRequest>存储请求
Elevator类:
存储电梯状态,实现电梯线程
InputManager类:
存储全局变量,标记输入是否已结束
此次作业中设置两个个共享对象——RequestTable和WaitTable
保证只有InputProcessor类调用RequestTable中的方法时才notify,实现InputProcessor类对Elevator类的单向notify在设计上比避免死锁
RequestTable
所有读写总请求表方法均加sychronized锁,仅允许一个线程访问共享对象。 当addRequest方法被调用时会notifyAll,唤醒等待中的电梯线程。
当策略类调用noMoreRequestToHandle方法发现请求表为空时则进入wait状态
WaitTable
所有读写总请求表方法均加sychronized锁,仅允许一个线程访问共享对象。 当addRequest方法被调用时会notifyAll,唤醒等待中的策略类线程。
当电梯调用noMoreThingsToDo方法发现请求表为空时则进入wait状态
电梯A可访问电梯A的请求表亦可访问电梯B的请求表,电梯B同理。由此基础实现两电梯的通信,当识别到孪生电梯在换乘层时,如果本电梯将到达换乘层,则不输出。
第二类reset请求时无法保证对于新创立的电梯B不分配请求。
回顾整个单元的作业流程,我没有进行大规模的重写,迭代过程相对平稳。核心功能如电梯的运行逻辑和请求处理机制保持了稳定性。在第三次作业中,我们主要通过创建新的类来实现新功能,对现有代码的改动较小。
稳定的内容
采用了生产者-消费者的设计模式。
实施了全局的自由竞争调度策略。
电梯的调度策略采用了look方法。
在线程安全方面,持续运用了synchronized关键字来加锁。
易变的方面
线程终止信号:每次作业在处理线程终止逻辑时都显得相对匆忙,使用了不同的条件来实现终止信号,有时为了一个信号还需要增加额外的类属性和方法。
Bug发现:主要依靠自我检测和同伴编写的测试程序来发现问题。
Bug调试:由于多线程环境的特殊性,无法简单使用断点调试,因此我更多地依赖于错误信息的观察,结合程序逻辑进行推理,以及通过在不同线程中添加打印语句来定位问题源头。
线程安全可以简单实现,确保程序无误,也可以为了提升效率而进行复杂的设计。使用synchronized锁可以确保数据共享的安全性,但这可能会显著降低多线程的执行效率,从而削弱了多线程的优势。通过与同学们的讨论和课堂上的学习,我逐渐认识到多线程编程的深度和广度,包括如何减小关键代码段的大小,如何更有效地使用读写锁、原子操作、信号量等高级工具,以及如何在保证线程安全的同时,优化线程间的通信、协作和调度,这些都是未来学习和实践中需要深入探讨的课题。
我认为我基本完成了层次化设计,从UML协作图可以看出实现了三层线程类和两层共享资源,我自我感觉像小时候的一种零食“超级3+2”,3层饼干2层夹心。
层次化设计的优势在于各类的职能比较清楚,并且可拓展性较强,尽可能的避免重构