这三次作业,发现自己无论采取怎样的架构,最后自己写的类都只有两个(除了给出的TestMain demo类)。
在类图中,都是只有三小块....架构复杂性完全没有变?
第一次作业:
类图:
设计模式:
第一次作业的架构是三次里面最清晰的——生产者消费者模式。
自己开始对于多线程完全没有思路的时候,《图解Java多线程设计模式》救了自己一命。
PS:这本书北航图书馆有电子馆藏,大家可以搜一下下载。(涉及版权问题就不直接放链接了)
具体如下:
模式角色 | 对应Class | 作用 |
生产者 | TestMain | 提供请求 |
消费者 | Copying | 不断地查询是否有新的请求需要执行 |
放有产品的桌子 | Elevator | 为生产者和消费者提供各种各样的接口 |
产品 | PersonRequest | (作业提供/自带) |
第二次作业:
类图:
设计模式:
第二次作业的架构可以说和第一次完全一样。但是这里自己遇到的一个新的考虑是是否需要换成观察者模式。
不再是每层都自己执行一遍有无请求的查找函数而是当有新的请求加入的时候,通知电梯这个请求的到来。这样不必每一层都执行查找函数。
关于观察者模式和消费者模式的区别:
目前看来,最大的区别在于一个是自己去看生产者有没有请求,一个是由生产者主动通知自己有没有请求。
这三次作业自己没有改变自己的架构,这样的带来的问题是CPU运行的时间会长一些。
目前还没有看到观察者模式在这次作业上有特别需要的地方。(因为电梯每一层都必须输出一个arrive信息。这样,电梯还是无法做到请求到请求的代码运行?)
第三次作业:
类图:
设计模式:
【请求拆分】
为了沿用自己之前的架构,第三次作业发现请求拆分比较方便。
即一开始就把一个电梯无法到达的请求拆成两个。
然后,优先寻找中转楼层为两个电梯之间的楼层。如果没有,选择中转楼层为1或者15 的最近楼层。
接下来的运行过程就和单电梯的调度策略是一样的了
踩到的坑:
1、LOCK对象与wait notify对象不一致就没有用了。
比如
如果是
synchronized (lock)
就必须在里面使用
lock.wait();
而非简单地
wait();
因为wait,表示是当前的this对象等待。
同理,notifyall也需要一致的对象。
2、电梯线程像尸鬼一样死不了,或者死的过早的问题。
如果主线程结束了,电梯线程怎么知道主线程结束了呢?
需要使用一个dead信号量,电梯线程不断地探测,如果为true,就自杀。
但是,出现了三个电梯,就比较难办了。因为一旦没有任务,电梯就wait了吗,不会主动探测到dead信号量。由于是事先分配所有请求,如果主线程输入没有了,其他电梯队列里面也不会有扔给自己的了,就可以线程结束。但是要小心电梯wait而检测不到dead信号量的问题。
复杂度比较与线程UML协作图:
第一次作业:
其实可以发现,第一次作业除了copy 的run函数复杂度比较高以外,实际上出现问题的就只有
elevator的stop方法,的确,由于几乎调用了这个类其他所有的函数,这个函数的独立路径的条数较多,模块的复杂性相比之下最大。
两个线程的协作,其中,输入线程(主线程)TestMain一直没有wait,不断地将请求加入请求队列当中并唤醒copying线程,copying线程就一直不断地执行请求,直到请求队列为空将自己sleep。
第二次作业:
renewdirect函数非常地冗长,主要的复杂度最大的函数。不过,这个函数的基本复杂度并不高(非结构化成分并不多),但是这个函数的模块判定结构复杂度很高,如果需要预防错误所需测试的最少路径条数较多,程序比较难以维护。
第二次作业线程之间的协作关系和第一次作业的协作关系基本一模一样。只不过增加了捎带的算法
第三次作业:
由于这次作业,代码需要处理的逻辑增加了不少但是自己依然只用了两个类,增加的函数也并不多,就导致每个函数都比较地冗长,复杂度较前两次作业大大地上升。
第三次作业,增加了线程池这个结构,管理三个电梯,由于只有一把锁,三个电梯在访问请求队列时会形成竞争关系。
主线程依然在不断地运行直到输入结束。
三个电梯各自的运行逻辑跟前两次作业相似,只不过最后结束的时候,并不单单要判断dead信号量,还要检查其他两个电梯的队列里面是否有自己需要处理的请求。
总体Analysis:
不出意料地第三次复杂度最高。第三次自己的几乎全部代码量都集中到Controller类里面了。分布非常地不均衡.....
conclusion:
感觉多线程在电梯作业中最核心的一点就是把握住lock的申请与释放问题。只要sleep就不再占有lock了。目前看来还是可以生命可以承受之重的代码。
希望下次不要再那么臃肿了.....