面向对象设计与构造 第二单元总结

第一次作业

UML类图

总体架构

参考exp3的架构,采用生产者-消费者模式,缓冲区为RequestQueue类,具体来说,输入线程通过对象waitQueue把请求交给调度器线程进行调度,调度器线程通过processingQueue将请求分配给对应的电梯线程。电梯的移动策略和捎带策略交给Strategy类处理。

调度器设计

第一次作业调度器其实可有可无,因为每个楼座只有一部电梯。但为了之后设计的考虑,仍然设计了调度器。调度器与线程的交互关系:输入的请求存在共享对象waitQueue中,调度器线程将其中的请求分配给对应电梯(楼座)的processingQueue

程序的BUG

强测和互测中均没有出现BUG。强测分数\(94.8787\),分数较低的主要原因是捎带策略比较差:采用类似LOOK的策略,但是捎带时没有判断请求方向是否与电梯运行方向一致。

第二次作业

UML类图

改进

本次作业加入了横向电梯,为了实现与纵向电梯的统一,将电梯的运行路线抽象为Route类;将运行策略简化,电梯取消了不动的状态;去掉了一些不必要的wait。由于采用自由竞争策略,整体没有太大的改动。

调度器设计

一开始,我也看不起自由竞争的策略,不理解为什么有相当一部分同学采用这种看上去非常低效的调度方式。但我周五努力了一天,尝试了各种不同的调度策略,仍然没有找到一种在多数情况下优于自由竞争的策略。为了性能考虑,只能采用自由竞争策略,于是调度器名存实亡,所做的仅为将请求分配给对应楼座的processingQueue,剩下的交给JVM进行调度。

程序的BUG

强测和互测中均没有出现BUG。强测分数\(98.1924\)

第三次作业

UML类图与第二次作业类似,仅多加入了RequestCounter类统计请求完成情况。

改进

仅加了按照基准策略拆分请求的功能。

调度器设计

与第二次作业相同。

程序的BUG

强测和互测中均没有出现BUG。强测分数\(98.4213\)

自己发现别人程序BUG所采用的策略

由于这个单元我没有hack过一次,所以介绍一下发现自己程序BUG的策略。
我的测试策略是:随机或手动造多种不同类型的数据,查看电梯的运行逻辑。我认为有效性还可以,比如我每个单元迭代的时候我基本上都会出现轮询的问题,随机一两组数据基本都能测出来;还有一个影响运行效率的BUG:一开始在getOneRequest的时候,若没有Request我会无脑等待,这导致在电梯开门后若没有人进来,电梯线程会等待而非先把现在电梯上的乘客送到目的地,这在我构造初始时投放大量请求,间隔较长时间后再投放大量请求这种数据时发现了。
与第一单元测试策略的差异之处:首先是测试的难度加大了,第一单元的测试可以借助Python中的sympy包比较简便地完成,而第二单元则必须自己写一个比较复杂的评测机。二是自动测试不一定能较高效地发现程序,一方面如果没有他人的程序,很难衡量自己的程序跑得算快还是慢,另一方面评测机只能判断输出的正误,而如果在调度策略上出现不影响正确性的BUG,很难发现。从这个角度上来说,这个单元应该更多地用静态查错。

同步块的设置和锁的选择

三次作业都大同小异,故放在最后总结。

为了解决官方输出包线程不安全的问题,将其加锁:

public class Output {
    public static synchronized void print(String s) {
        TimableOutput.println(s);
    }
}

对于共享对象,对其进行读写时全部调用对应类的方法,无需使用同步块语句

synchronized (Obj) {}

,只需要将其内部所有方法加上synchronized即可。

对于第三次作业,由于换乘的存在,当前楼座/楼层无情求时电梯线程也不能结束,故增加RequestCounter类统计当前请求完成情况:

public class RequestCounter {
    private int cnt;

    public RequestCounter() {
        cnt = 0;
    }

    public synchronized void inc() {
        cnt++;
    }

    public synchronized void dec() {
        cnt--;
    }

    public synchronized boolean finish() {
        return cnt == 0;
    }
}

UML协作图

心得体会

线程安全:

这是我第一次接触到多线程,之前也没有预习,上了第一次理论课之后还是云里雾里的,对多线程有了简单的认识但具体实现还是不太清楚。好在有实验课的代码,看完之后真是豁然开朗,于是采用那个简单的架构贯穿了这三次作业。至于线程安全问题,我遇到过的貌似只有轮询和死锁,同样是一开始只有个大概的了解,直到在自己的程序中出现,才有了一种大彻大悟的感觉,属实是“实践出真知”了。本单元比较遗憾的是,第一单元初入门多线程,第二单元花了大量时间测试各种调度策略,第三单元没有时间随便写了下,一直没有机会研究Java中各种处理多线程的方法和除生产者-消费者外的设计模式,只是在实验课上简单地了解一下,结果这个单元结束还是只会最简单的处理方法。

层次化设计:

这一单元我没有在架构上花太多时间,也没有重构过,迭代开发也不用修改上一版本的很多东西,感觉在这方面还是比较成功的。主要原因是一开始就参考了exp3的优秀架构并一直沿用,感谢助教!

posted @ 2022-05-01 16:27  Kakki_Haruka  阅读(51)  评论(1编辑  收藏  举报