前言
第二单元的三次作业围绕电梯展开,近一段时间里我们学习了多线程的相关知识,也体验了传说中的魔鬼电梯,我对多线程的理解也从一开始的仅仅了解名词到能够运用知识创造“电梯s”,虽然自以为在这一个月中收获不小,但自己也很清楚学习到的仅仅是皮毛。总的来说,这个单元的前两次作业比较简单,第三次作业则比较玄学,自己也因为感觉前两次架构不够可靠所以在第三次作业改动较大,借此次博客从以下几个方面对第二单元的作业进行总结与反思。
一、作业回顾
第一次作业
第一次作业是
第一次作业总的来说比较简单,通过ElevatorIn输入请求并存在请求队列RequestQueue中,采用FIFO原则,电梯每次只完成一条指令,当输入为空且请求队列为空时,程序结束。
公测及互测bug分析
第一次作业比较友好,主要是为之后的作业铺垫,在公测及互测中未发现bug。
自己在写第一次作业的时候出现了程序无法正常结束的问题,在ctrl d之后依然可以输入,只是程序不再对输入进行处理,后来发现是多个线程之间的通信信息没有设置好,这一点的发现对后两次作业的进行具有重要作用。
第二次作业
第二次作业是
第二次作业延续了第一次作业的设计,只是在Elevator类里增加了遍历请求队列以确定电梯行为的功能。
公测及互测bug分析
第二次作业在强测中有一个点的运行时间超过了Tmax,总的来说调度方法比较死板,比如此时电梯在3楼要去10楼,而此时有一个请求2号是从5楼到2楼,那么电梯会把2号运到10楼再送回2楼,这不符合实际情况。还有不足的是自己在优化上没有好好思考,未采取效率较高的调度方式。
第三次作业
第三次作业是
第三次作业相较前两次改动较大,有一个主队列接收输入,各个电梯分别有一个请求队列,由Controller负责分配调度,同时增加了处理换乘的Connect类,若是不能直达则将请求分为两个,人员到1楼或15楼进行换乘。
公测及互测bug分析
这次的公测和互测情况不那么理想,由于换乘的调度不当以及修改了电梯内部的运行方式,导致多个换乘指令便会出现StackOverflowError报错,一个原因是在设计架构的时候不够仔细,还有一个原因是自己在进行测试的时候太过敷衍,没有认认真真构造测试样例进行检查,也当做是收获了一个教训吧。
二、基于SOLID原则的评价
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 | 接口分离原则 |
1.SRP
类Input,Elevator,RequestQueue,Connect,Controller等较为合理的实现了功能单一性,但依然出现了个别类负荷过重的情况。根据单一职责原则,当一个类具有了多项职责,它需要被更改的可能性也随之增加。每次一个类的修改也会使得bug产生的风险增加。而通过集中职责与一点会使得风险被有效的限制。
2.OCP
开闭原则指出:“软件实体(classes, modules, functions etc.)应该对拓展开放,对修改关闭”。与SRP一样,这项原理通过限制对现有代码的更改来降低引入新错误的风险。跟第一单元三次作业相比,自己感觉在程序拓展性的处理上有了进步(或许是跟程序本身有关),虽然第三次作业跟前两次相比改动较大,但主要是增加模块处理新的需求,完成调度工作,比较符合开闭原则。
3.LSP,ISP,DIP
在这次作业中没有使用到借口和继承(排除Runnable),应该可以实现三个子类电梯,继承父类电梯,但是感觉这样的写法比较麻烦,也没有必要。
虽然在本单元作业设计中未使用到接口和继承,但是也算对SOLID原则有了初步的了解,对以后的作业也有指导作用,希望在之后的作业中我能渐渐向这五项原则靠拢。
依赖倒置原则(DIP)有两条声明。第一个是高级模块不应该依赖于低级模块。两者都应该依赖于抽象。第二部分规则是抽象不应该依赖于细节。细节应该依赖于抽象。DIP主要涉及到应用中层次化的概念,其中较低级别的模块处理细节的功能,较高级别的模块使用较低级别的类来实现更大的任务。该原则规定了在类之间存在依赖关系的情况下,应使用抽象(如接口)来定义它们,而不是直接引用类。 这减少了由较低级别模块的变化导致的错误,导致较高层的错误。 DIP经常在依赖注入中被使用。
三、关于互测
跟上一单元相比,感觉这个单元的互测难度是增大了的,不仅与楼层有关,还涉及到时间。在互测中,一开始是会用同学的评测机(感谢各位大佬!)对屋里的代码进行测试,偶尔会发现大家设计疏漏的地方(也会发现自己的bug)。时间充足的话会去分析其他同学代码的设计结构,会发现大家的实现方法都不太一样。比如在第三次作业中就看到有同学是针对三个电梯分别写了三个类,还有大家采取的不一样的调度策略,有的同学是FAFS调度,而有的同学的设计则是比较符合实际生活中的电梯运行模式。总的来说,我感觉互测能够给我们很多启发。
四、心得体会
1.线程安全
在多个线程访问共享数据时,会导致线程安全问题产生。
线程安全在三个方面体现:
1.原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作,(atomic,synchronized);
2.可见性:一个线程对主内存的修改可以及时地被其他线程看到,(synchronized,volatile)
3.有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序,(happens-before原则)。
总结起来,确保线程安全的设计有三个关键要素:
1.控制对象发布和共享;
2.共享对象设计为线程安全类;
3.保持简洁的线程类。
2.设计原则
在动手写代码之前,需要有整体的构思,要注意识别并发行为,简化类方法的职责,设计类之间的协同以及空间与时间的平衡。
在实现代码之后的设计检查中,除了要重视SOLID原则,我觉得在本单元中还需要关注以下几点:
1. 局部化原则,类之间不要冗余存储相同的数据,方法之间不能够出现控制耦合;
2.完整性原则,一个类需要提供针对相应数据进行处理的完整方法集;
3.显式表达原则,显式表达所有想要表达的数据或逻辑,不使用数组存储位置或者常量来隐含表示某个特定状态或数据;
不管在哪个阶段,都要提醒自己,正确性最重要!
3.反思
转眼间这学期已经过半啦,经过这三次作业,我对多线程有了更深入的了解,最近OS也在学习多线程有关的知识,两门课程都能互相补充到一些东西。自己在写代码的过程中,由于理论知识储备不够,常常会有力不从心的感觉,我想我还需要时间去积累理论知识并且学着应用。
道阻且长。