电梯调度编写(oo-java编程)

第二单元的问题是写一个关于电梯调度的程序。

需要模拟一个多线程实时电梯系统,从标准输入中输入请求信息,程序进行接收和处理,模拟电梯运行,将必要的运行信息通过输出接口进行输出。

主要锻炼学生的多线程程序编写能力。

由于需要实时的输入和输出,我们不得不采用多线程。

在这个单元中任务仍然被分为三个小任务:

①完成单电梯(随时允许输入)

②单电梯+(楼层增加负层,必须使用比先来先服务更加高效的算法)

③多电梯调度(增加重量限制、楼层停靠限制、换乘)


一、调度算法设计

单电梯的调度算法:

我在网上寻找调度算法后发现,网上大多的算法采用的都是静态算法,几乎没有动态算法。所以,我自己设计一个算法(或者说是参照实际使用中电梯的运转方式设计的算法),如下:

①查看该楼层是否有请求(包括进电梯和出电梯),有则开门转②,否则转③

② 让请求的人进出,并进行输出,所有请求进的人将这些人的出电梯请求同时加入电梯的请求序列中。关门转③

③沿电梯运动方向查看是否有任意类型的请求,如果有请求则向该方向运动一层转④,否则改变方向查看请求。如果在另一方向上有请求,则想这一方向移动一层转④。如果两个方向都没有请求则进程休息(wait),等待唤醒,唤醒后转④。

④如果不在输入任何需求则结束,否则转①。

多电梯的调度算法:

多电梯我觉得可以在原来的基础上进行修改,在调度器分配请求时通过某种方式将请求分给不同的电梯,然后每台电梯按照单电梯的调度算法进行运行即可。由于本人的多电梯的调度算法效率比较低,所以这里就不多赘述,可以参考其他同学的多电梯调度算法。


二、程序分析

对每次的程序使用MetricsReloaded进行oo度量,使用diagram描绘类图

重要符号意义说明:

  • ev(G)基本复杂度是用来衡量程序非结构化程度的.
  • Iv(G)模块设计复杂度是用来衡量模块判定结构,即模块和其他模块的调用关系。
  • v(G)是用来衡量一个模块判定结构的复杂程度,数量上表现为独立路径的条数。
  • LOC: Line of Code
  • NCLOC:Non-Commented Line Of Code

P1

oo度量

  LOC NCLOC 方法个数 属性个数
Elevator 183 169 5 2
Main 9 9 1 0
Midlist 26 26 5 2
OrderClass 46 42 2 7

电梯调度编写(oo-java编程)

diagram类图

电梯调度编写(oo-java编程)

Sequence Diagram

电梯调度编写(oo-java编程)电梯调度编写(oo-java编程)电梯调度编写(oo-java编程)

P2

oo度量

  LOC NLOC 方法个数 属性个数
Elevator 219 206 10 7
Main 11 11 1 0
Midlist 33 33 6 2
OrderClass 46 62 2 5

电梯调度编写(oo-java编程)

diagram类图

电梯调度编写(oo-java编程)

Sequence Diagram

电梯调度编写(oo-java编程)电梯调度编写(oo-java编程)电梯调度编写(oo-java编程)

P3

oo度量

  LOC NLOC 方法个数 属性个数
Chart  62 62 5 1
Elevator  307 278 13 14
Entry  8 8 1 0
Main  12 12 1 0
Midlist  367 322 21 19
Orderlist  46  42 2 5

电梯调度编写(oo-java编程)

类图

电梯调度编写(oo-java编程)

Sequence Diagram

电梯调度编写(oo-java编程)电梯调度编写(oo-java编程)电梯调度编写(oo-java编程)电梯调度编写(oo-java编程)

前面的时序图看着太复杂,而三次作业的结构大致一致,于是我做了一个简易版的sequence diagram。

电梯调度编写(oo-java编程)

优点:在第一次作业的时候就做好了基础的调度设计,是一个比较有效的设计,在随后的两次作业中修改的内容相对较少,也没有第一单元的重构情况,从类图上看,大体的结构并没有很大的变动。由于调度器不是一个进程,并发控制的难度比较低。

缺点:功能不够独立,在新需求出现时,常常要重新独立出一部分功能。设计时缺少借口等这类设计,导致一修改需求就会违背oo的设计SOLID原则。实际上第一次的设计从某种角度上来讲是存在瑕疵的,调度器不是一个进程,所以调度器在功能欲发复杂的情况下越来越庞大,但是却没有并行性,某种程度上限制了运行的性能。


 三、多线程的协同和同步控制

在第一次作业中,需要考虑如何停下自己的程序,我采用的是让中间的管道传递一个状态,即输入端是否关闭,如果关闭则再电梯的所有请求处理完后停止进程。

在第二次作业中,实际上并没有太多的改变(因为我第一次就已经做了优化),但是这次需要避免cpu轮循的问题。那么我在之前的基础上补充电梯wait的要求,应该是输入队列里没有东西,而且输入进程没有停止。

在第三次作业中,为了继承之前作业的架构,我并没有将调度器作为一个独立的进程。但是由于调度器中可能没有属于电梯的请求,这个时候电梯就应该wait,但是就算输入结束了也不能轻易的结束进程,因为有些人可能需要换乘(但这个需求可能还没有出现)。所以我做了下面一个表来处理这个相对复杂的问题。

电梯可以获得的请求 输入端

有无分割的请求

(换乘请求)

电梯动作
- - 运行
× - sleep
× - sleep
× 结束

四、分析自己程序的bug

第一次作业中,程序结束的控制计较容易出bug,因为在程序结束输入的时候(ctrl+D),仅凭借队列里是否有未服务的对象是不够的,还需要有一个判断标志,在电梯每次轮循后对输入是否结束进行判断,从而决定是否结束电梯线程。

第二次作业中,由于不能轮循(因为太占cpu时间),所以必须使用wait/notify方法,这个时候需要考虑好线程有没有可能会死锁,如何才能保证不死锁以及如何控制线程结束。(这三个问题是第二次作业中比较重点的bug/问题)

第三次作业,由于程序难度的增加,程序的复杂,轮循和,进程意外退出,进程没有被唤醒再次成为问题的关键。在这一次中,我最后应该将轮循和进程退出可能的情况进行列表推演,当当靠脑子想这件事情是很不靠谱的!

五、寻找他人bug(我不做互测)

多线程中,PV操作及其重要,关于线程之间的变量必须synchronize,可以着重看这一部分的代码,而且各种状态的传播也会在这一部分中,这一部分代码是最容易出bug的。

根据这次多线程的特点,应该着重考虑多线程的死锁、轮循这两个问题,着重分析他人代码中占用的位置。而且多线程的bug不一定能够触发,所以可能要多试几次。

相比于第一单元,bug的查找更加困难,因为有一些bug是概率性出现的,所以在测试中可以能需要对一个测试样例输入多次(或者选择不同的时间节点输入)。

六、SOLID原则分析

S.O.L.I.D是面向对象设计和编程(OOD&OOP)中几个重要编码原则(Programming Priciple)的首字母缩写。

SRP The Single Responsibility Principle 单一责任原则
OCP The Open Closed Principle  开放封闭原则
LSP The Liskov Substitution Principle 里氏替换原则
DIP The Dependency Inversion Principle 依赖倒置原则
ISP The Interface Segregation Principle 接口分离原则

SRP原则分析:在三次作业中,各个类的职责区分的非常清楚,启动线程、调度器线程、电梯线层、请求输入线程都能找到对应的类。

OCP原则分析:在这三次作业中(特别是第三次作业),我没有让三个电梯分别使用不同的策略(也就是说除了基础属性,其他他们都是相同的),所以不存在违背OCP原则的问题。

LSP原则、DIP原则分析:基于这次作业的难度,没有进行抽象和继承。

ISP原则分析:在设计的过程中,我并没有特别在意接口这个问题,所以我觉得这个单元的作业,在这个方面是有问题的。

七、心得体会

这次多线程的作业,让我全面理解了多线程。虽然在不需要涉及各种相对复杂的锁,但是仍然让我在大体上理解了多线程编写所需要的注意点。我希望接下来能够对我的编码进行指引,增加代码的限制,让我的代码更加工业化(能适应企业的要求)。

上一篇:M-自适应宽高样式


下一篇:Angular service, 服务