OO第二单元总结

OO第二单元电梯总结

架构模式

Hw5, Hw6, Hw7三次作业架构基本没有巨大变化,属于增量的叠加开发

hw5 一级生产者消费者模型with策略类分离

image

第一次作业, 我做了两种架构的尝试, 写了:

  1. 调度线程祭天型 单托盘

    image

  2. 带调度器线程的两级托盘

image

在尝试写了两种架构的基础上, 我分析了一下两种架构,

  1. 不对任何数据进行区分, 所有数据对电梯可见

  2. 是二级生产者消费者模型, 仅仅对每个楼进行划分,每栋楼之间的请求隔离, 楼内请求电梯间是互通的

在我的两种架构里, 第二种架构的scheduler线程仅仅负责取总托盘的请求,然后按楼区分发。本质上和你电梯直接去查找大托盘, 各取所需, 在功能上似乎没有任何区别。 反而调度祭天,减少了一个线程, 首先会简单不少, 其次, 每个电梯可见所有请求, 或许扩展性反而更好, 所以最终我选择了1作为三次作业的基本架构。

总之,对于架构的选择取舍就是:

要获取高效简洁解决问题的架构, 就得在调度自由度和懒癌(bushi, 简单度之间做取舍

​ ————沃兹基朔德

image

关于策略类的提取:

​ 体现了DIP依赖倒置原则高级模块不应该依赖低级模块,而是依赖抽象接口,通过抽象接口使用对应的低级模块

关于优化实现了:
  1. 量子电梯, 通过记录上次arrive & close 时间, 减少了wait以后,再被唤醒,可以快速的度过本层(其实就是拿一部分沉睡时间当作运动中的时间)比如:

        private void moveArrive() {
            try {
                
                Thread.sleep(max(400 + (lastArriveTime - System.currentTimeMillis()), 0));
                // Thread.sleep(400);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            building = (building + dir + 5) % 5;
            // arrive
            MyOutput.println("ARRIVE-" + buildingNameList[building] + "-" +
                    floor + "-" + elevatorId);
        }
    
  2. 开关门400ms恒定保持

  3. 先下后上,尽量哆啦人(这不是共识吗?但我和其他童鞋交流时发现他没有,姑且算是吧

hw6 增加了两个电梯子类的一级生产者消费者模型

image

这次作业中, 可以看到最令人神奇的就是:

  • 加入了横向电梯
  • 动态新增电梯
  • 不考虑换乘

这就意味着我们要为环形电梯设计新的的调度策略, 同时多个电梯这一次终于可以互相进行抢人, 这就带来了竞争和分配如何选择的问题

  1. 对于新增横竖电梯,还存在行为上的差异, 所以我搞了两个子类, 分别处理横竖方向的电梯

  2. 动态新增电梯, 这里其实暗示我们搞线程池, 然鹅我也现在才发现, 如果以后有机会可以搞个线程池, 其次由于选择了调度线程寄了天, 自然选择

  3. 提前为换乘打余量,我将托盘中的PersonRequest 改成了ConcurrentLinkedQueue<PersonRequest>链表,具体怎么换乘hw7来说

  4. 环形电梯调度策略:

    采用了类ALS基准策略, 效果还可以, 基本性能分都拿到了, 98+不戳的

    Look也写了,但是担心出bug, 所以没有采取这个策略对象, 用了ALS, 当然Look肯定写好了是更快的

hw7 带换乘的一级生产者消费者模型

image

可以看到基本没改什么, 就是在Input类中加入了一个换乘拆分函数

image

线程的协作简图

image

主要就是

  • 输入线程,接收到输出, 进行请求拆分, 或者创建电梯线程
  • 电梯线程, 在while循环中, 每次检测是否wait或者return, 此后决定上下人,并依据上下人开关门, 最后决定方向并前进

同步块和锁

总结分析三次作业中同步块的设置和锁的选择,并分析锁与同步快中处理语句之间的关系

三次作业架构基本保持了一致, 且同步块基本也保持了一致, 取最后一次作业的同步块讲解:

首先是 RequestTable.java中

// InputHandler 添加请求, Elevator送人到中转站时
public synchronized void addRequest(ConcurrentLinkedQueue<PersonRequest> a) {
        requests.add(a);
    }
// personLeft维护的是 没有送达目的地的人数
// InputHandler接收到请求时
    public synchronized void addPersonLeft() {
        this.notifyAll(); 
        personLeft++;
    }
// Elevator送人到目的地
    public synchronized void subPersonLeft() {
        personLeft--;
    }
// 输入结束
    public synchronized boolean isInputEnd() {
        return inputEnd;
    }
// 输入结束
    public synchronized void setInputEnd(boolean inputEnd) {
        this.notifyAll(); 
        this.inputEnd = inputEnd;
    }

// 上下是否没有请求了
    public synchronized boolean isUpDownEmpty(String buildingName) {
   		...
    }
// 同理
    public synchronized boolean isLeftRightEmpty(int floor) {
        ...
    }
// 前方是否有请求
    public synchronized boolean upDownRequestAhead(String buildingName, int floor, int velocity) {
		...
    }
// 人都送到了
    public synchronized boolean noPersonLeft() {
        return personLeft == 0;
    }


Strategy类木有锁, 不互斥的给出运动方向、上下人

public interface Strategy {
    public Vector<PersonRequest> getPickUpRequests(Vector<ConcurrentLinkedQueue<PersonRequest> inside,
                                                   int floor, int dir, int capLeft);

    public boolean turnAround(Vector<ConcurrentLinkedQueue<PersonRequest> inside, int floor, int dir, int capacity);

}

InputHandler中的锁:

// 用于判停,判wait   
private boolean checkWaitReturn() {
        synchronized (requestTable) {
            while (this.isempty() && requestTable.isLeftRightEmpty(floor)) {
                if (requestTable.isInputEnd() &&
                        requestTable.isLeftRightEmpty(floor) && requestTable.noPersonLeft()
                        && this.isempty()) {
                    requestTable.notifyAll();
                    return true;
                }
                try {
                    requestTable.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        return false;
    }

主要就是锁住了RequestTable 防止 check & modify, read & write, write&write 的线程危险, 使得其互斥。

还有就是电梯在运行时需要查看一下情况,其实也可以放在RequestTable类里。

调度器设计

hw5的一个版本里, 我的调度器主要就是负责按楼层分发, 没有任何特殊性,感觉名存实亡就是摆设, 所以就舍弃了调度器线程。

我的调度器线程没了, 调度主要就是自由竞争。

同时换乘请求拆分时, 优化了一部分基准策略, 按概率随机分配拆分楼层, 使得拆分楼层分布保持较为均匀

bug与测试 总结

强测&互测bug

三次作业总共出现了一个bug, 就是我第一次作业最后提交的时候一着急, 不小心把上下人逻辑搞错了, 导致可以承载的人数过多,wa了好几个点,真的心痛, 血的教训。

debug最重要的是按部就班的按debug流程来, 不要着急,不能东一榔头西一棒槌,都可能错过你的bug.

ps: 中测真的很弱, 感觉就是把你骗进来杀

​ ————沃兹基朔德

比如在那以后我就规定了这么一套流程,防止我心急或者漏了什么,导致bug被miss过去, 看着图一乐吧,毕竟我真是太粗了, 这些流程守好了, 别丢东西了

遇到的记忆深刻的bug

image

本地测试与测评机

测评机三次作业都做了, 还和小伙伴一块分享一块测试, 真不戳!评测机也是不断迭代hhh

从最开始只能测一个人,到支持多个人一起pk速度

唯一不足就是对于超时应该及时掐断, 我还没来及整, python多线程有时间学一波吧

心得体会

线程安全心得

线程安全的心得在于,关键在于设计线程安全类, 通过线程安全类来保证各个线程的行为是安全的, 比如通过锁的方法保证互斥, 通过资源也同时完成了线程间的协作通信。 关键在于选取合适的临界区, 保证线程安全的情况下尽量小。

层次化设计&设计原则

三次作业基本是迭代的, 每次作业都在上次的作业上或是继承, 或是新增了一些功能, 较好的实现了代码复用

SRP: 职责应该单一,不要承担过多的职责。体现在每个类只负责自己的行为, 比如:

​ 电梯只负责开关门, 上下人, 停止, 转向, 前进。

​ RequestTable仅仅负责,根据电梯的信息进行搜索并返回数据,以及数据的更删查改。

​ InputHandler仅仅负责拿到请求、处理请求

OCP:修改软件功能的时候,使得他不能修改我们原有代码,只能新增代码实现软件功能修改的目的。 可以从三次代码的结构是迭代的, 仅仅新增了部分代码, 实现代码复用。

ISP: 接口的内容一定要尽可能地小,能有多小就多小。 我将策略类的接口仅设置了2个方法,转向、上下人,最小限度的保持了策略类对电梯的控制

LSP, DIP: 体现在策略类的分离体现了 ,前面说过了,就不重复了

真情实感

做首诗纪念一下

电梯月

​ ——cywuuuu

欢送电梯月,

可惜有bug。

下次再努力,

争取bugfree!

posted @ 2022-04-25 23:23  cywuuuu  阅读(274)  评论(1编辑  收藏  举报