面向对象设计与构造第二单元总结
面向对象程序设计与构造第二单元总结
第一次作业
主要内容:
模拟单部电梯运行,要求人数限制、楼层限制、固定速度,并且含有RANDOM,MORNING,NIGHT不同时间的请求,需要分类设计电梯调度方案,考虑性能。
思路分析:
本单元作业中第一次作业其实最需要好好考虑,因为第一次的构架很重要,第一次做好了构架使得代码具有很好的拓展性花费了很多时间。整体分类分为三部分:输入队列、调度器、电梯;调度器扮演了中间角色,而输入队列和电梯构成生产者-消费者模式,设计为可稍带的调度电梯。考虑好整体框架之后就需要补充细节和框架联系了。
因为在这之前对多线程知识不够了解,所以先学习了JAVA多线程相关知识,然后学习观看了学长们的博客,开始构造!
-
框架:
生产者:
StartGo线程获得请求后加入队列RequestList调度器:
CenterControl控制请求队列和电梯运行的关系消费者:
Elevator线程自己有自己的运行特点 -
三种调度:
Random:先判断队列中是否有请求对象,并且根据自身的三种运行状态:up,down,stop以及电梯自身队列中的乘客状态获得主请求,向着目标运行;其中可以在运行过程中进行捎带请求;若请求队列中无请求对象则将自己电梯内部队列中的第一个乘客设为主请求。总结:这种算法调度简单,但是性能一般,只是实现了可稍带的原则,但是并没有优化可稍带的策略,笔者在提交之后想了可以通过楼层间距来判断最佳捎带的策略,或者先进行电梯模拟运行求得最优策略,但是没有实现,很可惜了。
Morning:此类型比较特殊,所有的请求都是从一楼进入,所以我设计的办法是来一个顾客进一个,并且重置电梯等待时间2s倒计时,来一个重置一次,直到电梯人满,然后将电梯中目的楼层最远的设置为主请求,但是笔者设计的时候出现了重大疏忽,导致自己的电梯在空等,所以性能方面大大降低,这也是第一次作业分数93中扣分最多的地方。
Night:此模式我的设计为从队列中获得最高楼层的请求,先接到他/她,然后向一楼出发,从高层向下捎带,这样的策略较为不错,性能也可以
主请求查询:
public boolean checkMain() { if (elevator.getPeopleNum() == 0 && queue.waitRequest() == 0) { return false; } else { if (queue.getPattern().equals("Night")) { if (first == 0) { synchronized (queue.getRequestList()) { int maxFloor = -10; first = 1; for (Customer customer : queue.getRequestList()) { if (customer.getFrom() > maxFloor) { maxFloor = customer.getFrom(); } } elevator.setGoalFloor(maxFloor); } } else { if (elevator.getPosition() == elevator.getGoalFloor()) { elevator.setGoalFloor(1); first = 0; } } } else { if (elevator.getPeopleNum() == 0) { elevator.setGoalFloor(queue.getRequestList().get(0).getFrom()); } else { elevator.setGoalFloor(elevator.getContain().get(0).getTo()); } } } return true; }
代码结构:UML类图如下

类复杂度:

其中调度器部分复杂度较高,耦合度也比较高,因为它的作用和很关键是两个线程的中间人。
BUG分析:
笔者在强测和互测中均未出现BUG,但是也发现了一些问题,比如在调度过程中偷懒了,放弃了一些优化手段,而且因为自己对多线程操作不够熟练,刚开始的时候使用synchronized经常出现问题,还好最后一一解决了。
在互测屋中也没有发现房友的BUG。
第二次作业
主要内容:
模拟单部电梯运行,要求人数限制、楼层限制、固定速度,并且含有RANDOM,MORNING,NIGHT不同时间的请求,而且增加了动态增加电梯的需求。
思路分析:
第二次作业整体来说对第一次作业的继承性很好,没有出现重构的现象,只是将消费者(电梯)设置为多线程,然后简单修改调度器的处理部分,然后增加一些特殊的操作就可以了,整体调度策略并没有修改,只是增加了某一个等待乘客是否被设置为主请求的属性,在其中由于是多电梯,采用了自由竞争的策略,谁能捎带谁就进电梯,然后若是到达目标楼层没有主请求,则重新设置开始竞争。
-
框架:
生产者:
StartGo线程获得请求后加入队列RequestList调度器:
CenterControl控制请求队列和电梯运行的关系消费者:
Elevator线程自己有自己的运行特点,变成了多线程情况 -
三种调度:延续第一次,主请求变为竞争形式,被捎带之后将重新选择目标。
主请求查询:
其中night部分为加锁请求此刻最远楼层顾客
public boolean checkMain(Elevator elevator) {
if (elevator.getPeopleNum() == 0 && queue.waitRequest() == 0) {
return false;
}
else {
if (queue.getPattern().equals("Night")) {
if (elevator.getN1() == 0) {
synchronized (queue) {
int maxFloor = -10;
elevator.setN1(1);
for (Customer customer : queue.getRequestList()) {
if (customer.getFrom() > maxFloor) {
maxFloor = customer.getFrom();
}
}
elevator.setGoalFloor(maxFloor);
}
return true;
}
else {
if (elevator.getPosition() == elevator.getGoalFloor()) {
elevator.setGoalFloor(1);
elevator.setN1(0);
return true;
}
}
}
else {
if (elevator.getPeopleNum() == 0) {
int flag = 0;
synchronized (queue) {
int i;
for (i = 0; i < queue.getSize(); i++) {
if (queue.getRequestList().get(i).getCaught().equals("-1")) {
queue.getRequestList().get(i).setCaught(elevator.getIdE());
queue.setNeedPick(queue.getNeedPick() - 1);
elevator.setGoalFloor(queue.getRequestList().get(i).getFrom());
flag = 1;
break;
}
}
}
if (flag == 0) {
return false;
}
return true;
}
else {
elevator.setGoalFloor(elevator.getContain().get(0).getTo());
return true;
}
}
}
return false;
}
代码结构:UML类图如下

类复杂度:

其中调度器部分复杂度依旧较高,耦合度也比较高,因为它的作用和很关键是两个线程的中间人。
BUG分析:
笔者在强测和互测中均未出现BUG。
在互测屋中也没有发现房友的BUG。
第三次作业
主要内容:
模拟单部电梯运行,要求人数限制、楼层限制、固定速度,并且含有RANDOM,MORNING,NIGHT不同时间的请求,而且增加了动态增加电梯的需求,而且出现了电梯类型(可达楼层不同)。
思路分析:
第三次作业整体上还是从第二次作业进行拓展,整体来说复杂度也不高,整体的调度并没有太大的变化,morning这里猛然间发现了前文中提到的疏忽,空等2s的现象,这里就抓紧修改了,night策略不变,random也没有什么变化,固定电梯前往接人,当初在考虑要不要设计换乘的时候纠结了很久,因为不管怎么样都会出现极优的情况和极差的情况,于是便设计为了不换乘形式,最后性能分99.3比设计换乘的同学大概低了0.5分左右。
-
框架:
生产者:
StartGo线程获得请求后加入队列RequestList调度器:
CenterControl控制请求队列和电梯运行的关系消费者:
Elevator线程自己有自己的运行特点,具有不同类型的状态,还是每个电梯一个线程 -
三种调度:延续第二次,主请求变为竞争形式,被捎带之后将重新选择目标。
主请求查询:
增加了与电梯类型是否符合的判断
public boolean checkMain(Elevator elevator) {
if (elevator.getPeopleNum() == 0 && queue.waitRequest(elevator) == 0) {
return false;
}
else {
int t = elevator.getT();
if (queue.getPattern().equals("Night")) {
if (elevator.getN1() == 0) {
synchronized (queue) {
int maxFloor = -10;
elevator.setN1(1);
for (Customer customer : queue.getRequestList()) {
if (customer.getType()[t] == 1 && customer.getFrom() > maxFloor) {
maxFloor = customer.getFrom();
}
}
elevator.setGoalFloor(maxFloor);
}
return true;
}
else {
if (elevator.getPosition() == elevator.getGoalFloor()) {
elevator.setGoalFloor(1);
elevator.setN1(0);
return true;
}
}
}
else {
if (elevator.getPeopleNum() == 0) {
int flag = 0;
synchronized (queue) {
int i;
for (i = 0; i < queue.getSize(); i++) {
if (queue.getRequestList().get(i).getCaught().equals("-1") &&
queue.getRequestList().get(i).getType()[t] == 1) {
queue.getRequestList().get(i).setCaught(elevator.getIdE());
elevator.setGoalFloor(queue.getRequestList().get(i).getFrom());
for (int j = 0; j < 3; j++) {
if (queue.getRequestList().get(i).getType()[j] == 1) {
queue.setNeedPick(queue.getNeedPick(j) - 1, j);
}
}
flag = 1;
break;
}
}
}
if (flag == 0) { return false; }
return true;
}
else {
elevator.setGoalFloor(elevator.getContain().get(0).getTo());
return true;
}
}
}
return false;
}
第三次作业刚开始出现了一个小bug,出现了中测一个点始终不过的情况,报错类型是225号乘客没有到达指定位置,但是本地测试始终没问题,每次这个点都错,最后才发现在多线程的设计中有一点需要格外注意,这里也提出来供大家引以为戒:
synchronized (requestList) {
if (checkThisFloor()) {
open();
getInOut();
sleep(OpenTime);
sleep(CloseTime);
close();
}
if (position == goalFloor) {
for (int i = 0; i < requestList.getSize(); i++) {
if (requestList.getRequestList().get(i).getCaught()
.equals(this.idE)) {
requestList.getRequestList().get(i).setCaught("-1");
break;
}
}
}
}
这里是一个电梯运行过程中的上电梯下电梯检查策略,看起来似乎没问题但是这里给出的代码块是整个synchronized块,其实有个违反多线程代码书写规范的小错误就是在对象锁中调用了一个函数,跳至外部,跳至锁外判断队列状态,而这个函数中也需要用到对requestlist的状态判断,所以导致了线程安全问题,我最后的解决办法是在需要调用的checkThisFloor()函数当中适当位置加上对于requestlist的synchronized锁,这样也是不规范的,只是我偷懒不想大幅度修改代码了(哈哈),规范要求应该是在锁当中不能进行函数的调用,这样容易出现疏忽导致上述的线程安全问题。
代码结构:UML类图如下

类复杂度:

BUG分析:
笔者在强测和互测中均未出现BUG。
在互测屋中也没有发现房友的BUG。
学习心得体会
1、线程安全与规范:
- 正确使用
wait,sleep,notify,notifyall等操作 - 正确使用
synchronized块,注意代码规范 - 格外注意以上两点的复合操作,避免出现线程安全问题
- 避免出现轮询的情况,仔细调试,防止测评出现CTLE
- 多个线程的不可预知性不方便调试的时候可以考虑原始暴力
sout大法,通过适当位置的情况输出来寻找bug
2、结构设计:
- 主要是生产者-消费者模式的理解与应用,还有单线程过度到多线程的掌握和拓展,第一次作业尤为重要,只要设计好框架,后面两次任务量都不会很大。
3、学习感悟:
本单元的三次练习感觉能够深入的了解面向对象的思路,按照面向对象的编码习惯,解决面向对象的问题。整体来说收获要大很多,在学习的过程中提升了代码能力,同时也掌握了java的语法,和多线程相关知识。

浙公网安备 33010602011771号