经过第一单元作业的训练,在做第二单元的作业的时候,要更加的有条理。但是第二次作业多线程的运行,带来了更多的运行的不确定性。呈现出来就是程序会出现由于线程安全问题带来的不可复现的bug。本单元的作业也让我更加认真的思考了性能和架构之间的关系,对于工程架构的设计有更进一步的认识.

历次作业分析和总结:

第一次作业:单电梯的傻瓜调度

类图如下:

第一次作业由于并没有性能要求并且刚刚接触多线程的运行,程序的结构简单并且多线程的各个线程之间并没有很多的交互。第一次作业设计中为了第二次作业的扩展,只是简单的开了电梯调度的线程,但是

只是简单的把输入的请求放到电梯运行的请求队列,没有进行其他的调度。并且由于刚开始并不熟练线程之间的交互,导致cpu的实际运行时间非常大。也就是,电梯不停止cpu一直被占用。

复杂度,耦合度,代码量表格如下:

上面表格中的信息也可以看出来,由于第一次作业的结构过于简单,并没有出现复杂度很高,耦合度高的情况,并且代码量也很小。

第一次作业中对于线程的run方法的重写过程,并没有清晰地费力出各个方法。导致run方法比较臃肿和相对复杂。

第二次作业:可捎带的单电梯调度

类图如下:

  • 本次作业支持了单电梯的可捎带调度。在程序的结构设计上,这次作业要比第一次作业复杂的多。在第一次作业的基础上:
  1. 我增加了把请求分解成为电梯的运行请求,也就是楼层请求的过程。而每个楼层请求包含着一个该层乘客上下电梯的人员链表。在乘客类中重写tostring方法,到该楼层遍历链表输出所有上下电梯的乘客。
  2. 增加了一层调度器,但是并没有另外开线程。也就是总调度器下有电梯单独的给电梯输送请求,并给请求排序的调度器Floor类(这是这次设计中的一个缺点,楼层请求类和调度器直接耦合在一块)
  3. 电梯从第一次作业的拿到一个乘客请求变为每次只是拿到一个楼层请求,只需要拿到请求,运行到目的路层,上下乘客,只是单单执行请求。
  • 关于第二次作业的线程交互和线程安全问题:

  第二次作业中的线程交互问题还比较少,只是涉及到调度器和当前电梯运行之间的协调。我才用的是电梯在上下楼层和开关门的时候放开电梯本身的类锁。调度器可以访问电梯当前的运行方向和楼层信息。并且这时候电梯放弃请求链表锁,调度器可以重新安排电梯目的楼层。电梯之间的交互并没有使用notify,只使用了wait(timeout)的形式。让电梯再等待一定时间之后可以重新主动要回锁。这种方法的好处是不需要去规划notify之后哪一个线程得到锁,会出现什么结果。

  • 关于第二次作业的电梯调度算法及其实现:

  第二次电梯的调度采用的是look算法。从强测的结果来看中规中矩,有运行比较好的点但是也有毫无性能优化的点。算法的实现过程主要体现在两层调度器的配合。主调度器主要完成当前请求的分解(分解为上楼层请求和下楼层请求)。再根据电梯当前的状态,给出匹配度。二层调度器接受楼层请求和匹配度,进行请求的正确插入。

复杂度,耦合度,代码量表格如下:

  • 度量分析

  程序的度量中可以看出,主要的复杂度和依赖度高的在于调度器这一块。而由于第二次作业的第二层调度器并没有和电梯的运行楼层请求剥离开来,因此其复杂度高,耦合性比较强。从以上的度量分析中可以看出调度器的结构有待优化提高。

  • bug分析及测试方法:

  在公测和互测中并没有出现bug。

  本次测试采用了自己打包的请求发射器和结果检查。数据集的构造并没有使用自动生成数据集。由于请求发射器存在一定的延时(0.05s左右),有时会出现构造的数据集输入后达不到预期测试效果。测试的时候构造测试用例主要针对于电梯的调度和电梯运行的线程安全问题。即是否会在一些运行的临界点出现调度问题或者是这时候出现新的调度而电梯运行错误。

第三次作业:多电梯的智能调度

类图和UMLjava类协作图如下:

  • 本次作业是支持多电梯的调度,相比第二次作业:
  1. 最大的改变是把电梯的第二层调度器剥离出来,并且构造成了一个新的线程类。
  2. 增加了请求的分割过程,即解决换乘问题。本次作业中采用的是确定唯一换乘方案:总调度器在接受请求之后,确定换乘方案,会把请求放到相应电梯的乘客池。因此,换乘方案是无法更改。并且本次的换乘方案最多只会涉及到两部电梯。
  3. 修改第二层调度器,支持换乘请求的正确执行。在拆分出换乘请求之后,可能会出现乘客还没有到换乘地点,电梯已经输出乘客换乘上了电梯的情况。因此修改了二层调度器的运行和电梯 上下乘客的方法。并且给每个乘客类增加了是否可执行,以及是否已经执行的属性。
  4. 通过协作图分析:输入处理和总调度器共享原始请求队列,总调度器取出队列请求,确定合适换乘方案之后,把请求直接分解成为乘客上下的乘客类加入到二层调度器的乘客池(PassengerPool),二层调度器每次需要遍历寻找距离当前电梯楼层最近的可执行乘客类,同楼层乘客组成为一个楼层请求。合理插入到电梯运行队列。电梯直行之后,把已经执行的乘客标记为已经执行,而乘客的可执行属性取决于他的前趋请求是否完成。
  • 线程交互和线程安全问题:

  第二次作业的线程交互增加,也是因为线程数量的增加和电梯的限制增多。本次作业的设计和修改过程中多次出现线程的死锁:电梯拿到的请求所有乘客请求都不能执行,而调度器却总是认为该请求是当前应该执行的有利于性能的请求。因此两者都无法继续运行出现死锁。还有由于电梯容量限制导致的死锁。之后,通过修改调度算法(牺牲性能QAQ)解决了这一死锁问题。

  • 本次作业的调度算法和实现:

  本次作业的调度算法在look的基础上,基于程序的死锁问题进行了一些改动。由于本次作业很大程度上放到了正确性的完成(强测还是跪了一个点),性能并没有做很好的实现和优化。

复杂度,耦合度,代码量表格如下:

  • 度量分析:

  由本次作业的度量:调度器中存在大量的循环和判断结构,复杂度高。第二层调度器中结构并不清晰,其run方法没有实现各个过程的分离。而对passengers类修改之后,其封闭性下降,很多属性容易被修改且不易察觉。

  • bug分析和测试方法:

  本次作业中出现电梯的运行问题。在处理当前楼层数字为零的情况出现bug,会在同一楼层arrive两次,在强测的时候被测试出来。测试中没有出现线程安全问题。互测的时候采用和第二次相同的策略。但是这次测试过程中,重在针对于电梯超载和请求换乘方面构造测试数据。在互测中发现由于换乘的问题出现线程安全问题。

debug方法:

  在多线程程序的编写过程中,由于不可复现bug的出现以及多线程的运行无法使用断点来定位bug,debug的难度有所提升。并且debug的方式也回归到了原始的打印输出定位bug。在本单元的作业中由于输出结果的检测会忽略err流的输出,所以debug的时候利用这一特点,把需要的信息输出到err流中,在不影响评测情况下可以输出运行信息并且不需要反复的注释掉输出语句。

作业的心得体会:

  本单元作业的完成过程中,随着作业的复杂度不断地提升,我并没有保持很好的设计架构的清晰和独立性。并且由于第二次作业的二层调度器的设计不独立导致第三次作业在原来思路上扩展多电梯的过程中走到死胡同不得不把第二层的调度器进行重构。相比于完成第一单元作业的过程,代码重用已经有所提升,程序的可扩展性也有所提升。但是程序的架构设计仍然存在不足,并没有充分的考虑耦合性问题。作业中存在着耦合度比较高的类。这也同时造成在修改程序的时候互相影响,bug增多。架构设计过程基本遵循单一功能原则。第三次作业中的乘客类的重构破坏了开闭原则。

关于线程安全,到第三次的作业会明显感受到线程不安全带来的问题。在多线程的设计中不只是要考虑访问数据的安全性,更要着重的考虑线程之间的通信问题。要从线程的设计方面,正确设计线程之间的有效通信,避免死锁问题。否则线程的不安全问题更是会带来一些不易复现的bug。再者就是要平衡好架构和性能的问题。好的架构和好的性能不是互相冲的方面,相反好的设计架构更容易构造出更优性能的程序。在多线程架构的设计中,一个架构清爽,安全性高的线程也更容易做出高性能程序。

OO第二单元总结(多线程的电梯调度)的更多相关文章

  1. OO第二次博客作业——电梯调度

    OO第二次博客作业——电梯调度 前言 最近三周,OO课程进入多线程学习阶段,主要通过三次电梯调度作业来学习.从单部电梯的傻瓜式调度到有性能要求的调度到多部电梯的调度,难度逐渐提升,对同学们的要求逐渐变 ...

  2. OO第二单元总结——多线程电梯

    第五次作业分析 1.设计策略 调度器采用单例模式,内部设请求队列,对请求队列的一切操作(查.增.删)都在调度器内完成,且都要求串行,从而确保线程安全.接收器和电梯是两个线程:接收器接受请求调用调度器来 ...

  3. 2019年北航OO第二单元(多线程电梯任务)总结

    一.三次作业总结 1. 说在前面 对于这次的这三次电梯作业,我采用了和几乎所有人都不同的架构:将每个人当作一个线程.这样做有一定的好处:它使得整个问题的建模更加自然,并且在后期人员调度变得复杂时,可以 ...

  4. OO第二单元多线程电梯总结

    OO第二单元多线程电梯总结 第一次作业 设计思路 Input为输入线程,负责不断读取请求并将读到的请求放入调度器中. Dispatcher为调度器,是Input线程和Elevator线程的共享对象,采 ...

  5. OO第二单元——多线程(电梯)

    OO第二单元--多线程(电梯) 综述 第二单元的三次联系作业都写电梯,要求逐步提高,对于多线程的掌握也进一步加深.本次作业全部都给出了输入输出文件,也就避免了正则表达式判断输入输出是否合法的问题. 第 ...

  6. 电梯也能无为而治——oo第二单元作业总结

    oo第二单元作业总结 一.设计策略与质量分析 第一次作业 设计策略 在第一次作业之前,我首先确定了生产者--消费者模式的大体架构,即由输入线程(可与主线程合并)充当生产者,电梯线程充当消费者,二者不直 ...

  7. OO第二单元电梯线程系列总结作业

    电梯系列第一次作业 功能描述: 傻瓜电梯无需考虑超载捎带 线程模式: Producer-Consumer Pattern 思路: 第一次作业是一个傻瓜电梯,分别有一个生产者生成电梯指令(也就是Inpu ...

  8. oo第二单元作业总结

    oo第二单元博客总结 在第一单元求导结束后,迎来了第二单元的多线程电梯的问题,在本单元前两次作业中个人主要应用两个线程,采用“生产者-消费者”模式和共享数据变量的方式解决问题.在第三次作业中加入多个电 ...

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

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

随机推荐

  1. Mongodb字段自增长

    MongoClient client = new MongoClient("mongodb://xxx.xxx.x.xx:27017"); var mongServer = cli ...

  2. Git一些简单但非常重要并常用的操作命令

    1.将本地与github进行关联配置 生成公钥 ssh-keygen -t rsa -C "jiasheng.mei@hpe.com" 将公钥拷贝到github中 在公钥同文件夹( ...

  3. Redis高可用技术解决方案总结

    一.常见使用方式 Redis的几种常见使用方式包括: Redis单副本: Redis多副本(主从): Redis Sentinel(哨兵): Redis Cluster: Redis自研. 二.各种使 ...

  4. Linux下执行Java程序报错

    在linux下编译java程序,执行javac编译生成class文件时,在centos7终端输入如,javac hello.java    会提示未找到指令,但用java -verison测试环境变量 ...

  5. SparkCore| 算子

    RDD RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是Spark中最基本的数据抽象.代码中是一个抽象类,它代表一个弹性的.不可变.可分区.里面的元素可并行 ...

  6. angular笔记_10

    过滤器 currency:1:2:3                     与货币相关 第一个参数是符号 第二个参数是保留小数点几位 number:1                         ...

  7. NEO智能合约开发(一)不可能完成的任务

    悬赏任务 兹有如下合约 public static object Main(string method, object[] args) { if (Runtime.Trigger == Trigger ...

  8. Python——Redis相关知识

    一.连接 Redis import redis 连接方式:redis提供了2个方法 1:StrictRedis:实现大部分官方的命令 2:Redis:是StrictRedis的子类,用于向后兼容旧版的 ...

  9. 201771010126 王燕《面向对象程序设计(Java)》第七周实验总结

    实验七 继承附加实验 实验时间 2018-10-11 1.实验目的与要求 (1)进一步理解4个成员访问权限修饰符的用途: private--私有域或私有方法:只能在定义它的类中使用 public--公 ...

  10. __x__(31)0908第五天__导航条的练习 <ul> 版本

    效果图:  html代码: <!doctype html> <html> <head> <meta charset="utf-8" /&g ...