面向对象课程第二单元博客总结
单元总览
本单元我们学习了Java多线程。通过电梯这一现实情景,体会了如何利用多线程机制完成多个并发任务以提高CPU利用率;对于线程安全有了一定概念,在实践中认识到多线程使用共享数据会面临的数据安全问题,并学会了使用同步块和锁来设计线程安全的程序。
与此同时,我们还学习并实现了生产者-消费者模型、单例模式、主从模式、流水线模式等,对于面向对象的设计模式有了更深的理解。
同步块与锁
在作业中我既使用过同步块,也使用过锁,包括条件锁和读写锁。
经过理论课的学习和课下阅读Java编程思想,我所理解为synchronized为JVM实现的简单的锁,(在目前内部实现的优化基础上)在简单的应用场景下性能也并不低于lock。
而lock需要显式地释放,同时提供了条件锁、可重入锁(包括读写锁)等更,有更多操作空间的类别。例如通过条件锁我们可以更容易避免死锁,而通过读写锁可以提高我们代码的运行效率。
在作业中,我的设计中存在数据安全问题的主要是“生产者-消费者”模型中的“托盘”部分,具体到我的代码架构,就是“楼座”/“楼层”类(一级托盘)和“站”类(二级托盘)。
在第一次作业中,我在一级托盘中使用了条件锁。当时对于锁的使用不熟,使用条件锁除了可以完成需求外,对我而言使代码逻辑更清晰。而二级托盘设计简单,直接使用synchronized关键字。
在后来的作业中,由于课上讲了读写锁,且指导书建议使用,于是将二级托盘改为读写锁实现。可能由于作业情景下构造出的数据竞争并不强烈,或者也有现在JVM对synchronized关键字实现已有较好优化,我并没有体会到读写锁能带来明显的性能提升。但确实通过实践掌握了读写锁的使用。
调度器
第五次作业为单部电梯,不涉及电梯间调度策略。我并没有设计调度器,每部电梯实现look策略;一级托盘可获得所有二级托盘排队信息,而电梯每一步从一级托盘获取运行方向。
第六次作业加入了多部电梯和横向电梯。我的横向电梯单部运行策略为只要电梯内有人则方向不变,捎带最短路为同向的乘客,无人时选择最近两栋排队人数更多的防线运行。而电梯间采取自由竞争,其在表现上并不显示逊于调度器,且实现难度低,依然没有单独设调度器。
第七次作业增加了换乘需求,我新增了人类,采用至多一次横向换乘的策略,设计惩罚函数用以选择换乘层。于是很自然地设计了针对人的调度器,完成请求拆分(选择换乘站)并控制请求的流水处理。
架构分析
现对三次作业迭代过程做以总结。
作业5
第一次作业是单部电梯。如前所述,不涉及调度策略问题,存在线程安全问题的类只有生产者和消费者读写托盘的问题。
第一次作业UML类图如下:
第一次作业UML协作图如下:
作业6
第二次作业相比第一次,一是增加至多部电梯,二是除纵向外增加了横向电梯。
第一个改变引入了调度问题,并且增加了线程不安全场景,即多部电梯同时对托盘的访问。对于第二个改变其实并不带来额外问题,因为横向和纵向电梯相互独立,除横向可循环运行外,无论是电梯还是托盘都和纵向无太大差别。
本人体会是:大部分时间还是在一开始的分析问题和确定设计思路,一旦想清楚了开始动手,实现就是很自然的事。
第二次作业UML类图如下:
第二次作业UML协作图如下:
作业7
第三次作业相比第二次,一是增加了换乘需求,二是增加定制电梯。首先第二点不难实现,只要通过工厂模式很容易做到。而对于第一点,我的思路如前所述,而关于人的换乘层选择,我所建立的惩罚函数,除距离外还考虑了当前的电梯情况(种类和个数),即运载能力。
特别地,我的换乘策略是静态的,即在最开始接到请求时即确定,中途不再改变,这是对于运算复杂程度和时间估计准确程度的tradeoff。而惩罚函数没有考虑排队人数等因素也是出于此原因。加入这些考虑不会明显提高策略效果,事实上策略效果本就和数据有较大关联,而计算的复杂程度却上升不少。
第三次作业UML类图如下:
第三次作业UML协作图如下:
问题总结
本单元我在第六次作业中互测被找出了bug,是会出现超载情况。主要原因是在充分测试并提交过一版代码后,又突然决定换策略,在改动的地方和原版衔接出现问题,并且没有再做充分测试。比起代码问题本身,觉得自己这种作业模式潜在问题更大。
以及第七次强测出现了ctle问题,分析可能问题出在横向电梯,新来一个请求会唤醒所有电梯,包括不可达的,不可达的电梯于是会不断寻找请求。
互测策略
互测策略主要是自动化测试。
自动化测试对于自测和互测的流程相同:实现数据生成器和评测机->编写自动运行脚本->挂起->去做其他事,等待查看日志就好啦!
心得体会
经过第二单元的练习,我们不仅学习了java多线程的知识,并且对于迭代开发逐渐熟练了。在研讨课的交流中发现很多同学都和我一样,每次作业都只在上次的基础上做改动和增量开发,很少再有重构现象出现。当然也和第一单元每次需求变化较大,难以预测有关,但我们确实也在两个单元的实践中逐渐养成一些好的习惯,并且尝试运用了课内学到的设计原则,优化自己的代码架构。
我个人和可见的身边一些同学也逐渐规范了自动化测试的流程并掌握好了节奏,不再“面向评测机编程”。
以及一些个人的经验总结。
首先是在提交截止前(未必是死的ddl,也包括自己的,比如此后虽然能改但自己没时间了)不要轻易做大改动。
曾经有人和我说,“每一次commit都应该慎重”。学生如我彼时并未入耳。
还有,适当的沟通交流是有益的。诚然一遇到问题就依靠老师、助教、大佬毫无意义,但走向闭门造车的另一个极端也无益于快速的进步。
这里感谢交流调度策略、合作自动化测试的群友们。与他们的交流帮助我扩展了自己的思路,并且有效push了我不要摸。