一、架构设计迭代

1.hw5

1.1 类图

图片

1.2 时序图

图片

1.3 架构分析(变不变)

深入贯彻落实**”今朝有酒今朝醉”的原则,在博览博客之后,发现都说去年LOOK+自由竞争是活少分高**的不二选择✧(≖ ◡ ≖✿)

我果断加入LOOK大家庭,结(zhao)合(chao)实验代码,草率搓出了第一版

  • Main运行InputThread输入线程、Schedule调度器(我其实之前耦合在输入线程里面)、Elevator电梯线程

  • 共享的数据类型只有Pesron记录人员请求信息、RequestTable管理人员请求信息

  • 策略类Strategy,采用LOOK算法

梦幻开局,我还天真的认为:电梯月?就这??(๑>ڡ<)☆

1.4 调度器设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public void run() {
while (true) {
if (mainRequestTable.isEmpty() && mainRequestTable.isEnd()) {
for (RequestTable requestTable : verticalRequestTables) {
requestTable.setEnd(true);
}
return;
}
Person person = mainRequestTable.getOneRequest();
if (person == null) {
continue;
}
int eleId = person.getBeginElevator();
verticalRequestTables.get(eleId - 1).addRequest(person);
}
}

在这次作业没什么存在性的东西,直接按照输入分配

1.5 同步块与锁

RequestTable里的所有方法统统上锁一劳永逸✧ (≖ ‿ ≖)✧

Strategy类里遍历RequestTable里容器的时候别忘上锁,不然有几率触发Java.util.ConcurrentModificationException

1
2
3
4
5
6
7
8
9
10
11
12
public boolean reqSameDirection(int eleFloor, boolean eleDirection) {
//请求队列非空已判断
synchronized (request) {
for (Integer i : request.getReqMap().keySet()) {
if ((i > eleFloor && eleDirection) ||
(i < eleFloor && !eleDirection)) {
return true;
}
}
}
return false;
}

2.hw6

2.1 类图

图片

2.2 时序图

图片

2.3 架构分析(变不变)

适逢清明假期,周一得知不能使用自由竞争的我缓缓裂开 º·(˚ ˃̣̣̥᷄⌓˂̣̣̥᷅ )‧º·˚

抛开调度策略不谈(随机是我最后的港湾),重构启动!!!

本次作业新增RESET,将重构总结为四步

  • 输入线程得到reset请求->mainRequest新增resetRequest请求队列数据结构

  • 调度线程分配reset请求->eleRequest新增resetRequest数据结构

  • 电梯类加入reset执行过程->放人->输出begin->返回请求->改电梯属性->输出end

  • 策略类新增RESET建议

1
2
3
if (request.getResetRequest() != null) {
return Advice.RESET;
}

2.4 调度器设计

心比天高不自量力,遂开始研究影子电梯,苦于前人记载较模糊,只有“用计时代替sleep”,完全不会做(╯#-_-)╯╧═╧

不想写深克隆也不想考虑是不是线程安全(我至今觉得下面的代码有问题就是没被hack到hhh)

写了一个丑陋的shadow类,来负责计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public Shadow(Elevator elevator) {
//一些有的没的。。。
this.time = 0;
//抽象开始了 我感觉线程不安全hh
ArrayList<Person> reqArr = new ArrayList<>();
reqArr.addAll(elevator.getEleRequest().getReqArr());
this.eleRequest = new RequestTable();
for (Person person : reqArr) {
eleRequest.addRequestClone(person);
}
this.eleMap = new HashMap<>();
ArrayList<Person> elePerson = new ArrayList<>();
elePerson.addAll(elevator.getElePersonArr());
for (Person person : elePerson) {
int toFloor = person.getToFloor();
if (eleMap.containsKey(toFloor)) {
eleMap.get(toFloor).add(person);
} else {
ArrayList<Person> persons = new ArrayList<>();
persons.add(person);
eleMap.put(person.getToFloor(), persons);
}
}
}

参考前人博客遍历6个电梯,找出能够使得新请求加入后整体运行时间结束最早的进行分配

丑陋抽象的实现了“影子电梯”,为第三次无法完全模拟电梯行为埋下祸根( >﹏<。)~

2.5 同步块与锁

1
2
3
4
5
6
7
8
9
ResetRequest resetRequest = mainRequestTable.getOneReset();
if (resetRequest != null) {
resetElevator(resetRequest);
}

Person person = mainRequestTable.getOneRequest();
if (person != null) {
dispatch(person);
}

新增对应调度器内的resetElevatordispatch方法分别上锁,确保不会同时执行

2.6 结束条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if (mainRequestTable.isPersonEmpty() && mainRequestTable.isResetEmpty()
&& mainRequestTable.isEnd()) {
if (isFinished()) {//每部电梯所有请求处理完毕 reqArr.isEmpty() && resetRequest == null
for (RequestTable requestTable : eleRequestTables) {
requestTable.setEnd(true);
}
return;
} else {//避免轮询
try {
sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
  • mainRequestTable所有请求处理完毕(reset & person)

  • 输入结束

  • 每部电梯所有请求处理完毕(reset & person)

3.hw7

3.1 类图

图片

3.2 时序图

图片

3.3 架构分析(变不变)

本周新增DCElevator,对此我强烈建议成立影子电梯受害者联盟 º·(˚ ˃̣̣̥᷄⌓˂̣̣̥᷅ )‧º·˚

秉持着菜就多练的原则,本周我灵活运用达芬奇睡眠法,倾情加入Buffer神教,继续深耕影子电梯

butttttttttt 缝缝补补到周六早八,还是有bug………

一怒之下,转身向随机算法+随机地大小睡走去

if u wanna sleep ,u must sleep ,著名哲学家,在周六九点终于入睡的汀小姐如是说道

3.4 调度器设计

本来我通过罚时粗糙模拟了Gemini电梯的运行时间,但遇到了如下问题

1
2
3
4
//debug实录
//加锁了??你一被我深克隆的东西怎么还会被外界改??反思
//计算时间 改了一天还有问题??
//人怎么还要上锁啊?????

今天交了一波强测,发现全AC了,世界是一个巨大的笑话 ——4.19

于是转身向随机走去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public synchronized int randomDispatch(Person person) {
Random random = new Random();
int id = random.nextInt(6) + 1;
synchronized (电梯表) {
synchronized (A电梯表) {
synchronized (B电梯表) {
//......
if (isChange) {
person.setNeedChange(true);
}
if (isA) {
Output.printReceive(1,geminis.get(index).getId(),person.getId());
geminiAReqs.get(index).addRequest(person);
} else {
Output.printReceive(2,geminis.get(index).getId(),person.getId());
geminiBReqs.get(index).addRequest(person);
}
} else {//普通电梯
if (eleRequestTables.get(id - 1).getResetRequest() != null
|| eleRequestTables.get(id - 1).getDcRequest() != null) {
return -1;
} else {
Output.printReceive(0,id,person.getId());
eleRequestTables.get(id - 1).addRequest(person);
}
}

}
}
}
return 0;
}

3.5 Gemini电梯防碰撞

仿照RESET指令,很容易复现DCRESET,区别在于DCRESET在1.2s执行结束后我选择终止电梯线程

原电梯线程在调度器里新建Gemini(双子星!姐妹儿英语真有水平),管理新建AB两个电梯

继续沿用LOOK策略,因为我只要确保人员分配正确、运行特判就可以让它们在现有架构下正确工作,我们要做的是对Gemini电梯的移动特殊规范一下(电梯防撞)

最关键的电梯防撞:

我起初想不要让电梯占用TransferFloor,来了干完活就走,但人总是喜欢自己为难自己(๑>ڡ<)☆

探索之后,我写出了这样的shit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public void moveSpecial() {
if (Gemini电梯 准备来Transfer) {
while (floor.isOccupy()) {
floor.occupyOneAway(type);
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (floor) {
floor.setOccupy();
try {
sleep((long) (1000 * moveTime));
} catch (InterruptedException e) {
e.printStackTrace();
}
eleFloor = stopFloor;
Output.printArrive(type, id, eleFloor);

Advice advice = strategy.getAdvice(eleFloor, elePersonNum,
eleDirection, eleMap, eleMaxNum,
type, stopFloor, elePersonArr);
if (advice == Advice.OPEN) {
openAndClose();
}
}
return;
} else if (Gemini电梯 准备离开transfer) {
//走
floor.awayTransfer();
return;
}else{
//正常行动
}
}
  • 设置一个floor类来表示换乘层是否占用,内存AB两电梯的请求表

  • 如果A想进入换乘层,floor被占用,就向请求表发送离开换乘层的请求,Asleep一下

    另一部电梯(B)离开后, floor.awayTransfer()设置换乘层不被占用标志

    此时A睡醒了进入换乘层进行放人操作

  • 如果A主动离开换乘层(有人需要接), floor.awayTransfer()设置换乘层不被占用标志

一睡到底逃避锁的竞争,不愧是我T_T

直到今天复习OS的时候看到进程互斥的软件实现,我有种似曾相识的感觉……这错误集锦是在骂我!!!

3.6 结束条件

和上次相比新增changeMan == 0

分配去换乘的乘客要坐上最后一班列车,我们才能放心和OO第二单元告别啊º·(˚ ˃̣̣̥᷄⌓˂̣̣̥᷅ )‧º·˚ ~

1
2
3
4
5
6
public static synchronized void decreaceChangeMan() {
changeMan--;
}
public static synchronized void addChangeMan() {
changeMan++;
}

二、bug分析

1.hw5

de出来的bug

可恶的Java.util.ConcurrentModificationException

线程不安全de遍历RequestTable里容器的时候一遍删除一遍遍历,别忘记上锁

第一次作业差点寄在这里了

1
2
3
4
5
6
7
8
9
10
11
12
public boolean reqSameDirection(int eleFloor, boolean eleDirection) {
//请求队列非空已判断
synchronized (request) {
for (Integer i : request.getReqMap().keySet()) {
if ((i > eleFloor && eleDirection) ||
(i < eleFloor && !eleDirection)) {
return true;
}
}
}
return false;
}

强测和互测

均没有hack和被hack 我们都好优秀(*>◡❛)

2.hw6

de出来的bug

  • 电梯最大人数忘记改居然还能过中测????
  • RESET_BEGIN在人员请求返回主表之后才输出

强测和互测

强测多亏了伟大的影子电梯

互测进入了围城必阙副本

一时齐发众妙毕备!!!!因为设置RESET电梯不接客所以大量请求就会涌入唯一一部不在RESET的电梯T_T

1
2
3
4
5
6
7
8
9
10
[1.0]RESET-Elevator-6-3-0.6
[49.5]RESET-Elevator-1-3-0.6
[49.5]RESET-Elevator-2-3-0.6
[49.5]RESET-Elevator-3-3-0.6
[49.5]RESET-Elevator-4-3-0.6
[49.5]RESET-Elevator-5-3-0.6
[49.5]1-FROM-2-TO-11
[49.5]3-FROM-3-TO-11
[49.5]3-FROM-3-TO-10
。。。

这种数据我一刀连上自己能刀7个人º·(˚ ˃̣̣̥᷄⌓˂̣̣̥᷅ )‧º·˚ (PS:我是文明人,这刀我就使一次)

此外还发现了一些同学reset调度上的一些bug

真是一次快乐刀人的体验 成功率高达33.3%╰ ( ´⌣` )╯

3.hw7

de出来的bug

AB电梯结束在停靠层,要在OVER之前先请它离开停靠层

1
2
3
4
5
6
7
if (request.isEnd()) {
if (type != 0 && eleFloor == stopFloor) {
return Advice.AWAY;
} else {
return Advice.OVER;
}
}

de不出来的bug

  • 影子电梯内请求表即使深克隆也会莫名其妙被篡改
  • buffer内的人减数分裂了
  • 影子电梯初始化时原电梯加锁后还会被篡改

强测和互测

  • 伟大的随机没有bug(๑ơ ₃ ơ)♥

  • 房间里有最后一条RESET就爆炸的地雷

  • 还有同学电梯防撞做的不到位(要我说就该随地大小睡)

  • 还有同学调度器写的有问题,有请求处理不完就覆盖了,但抽不到这张bug卡T_T

4.debug方法

  • 首先写之前确保自己的运行逻辑没有漏洞
  • 其次使用大量数据来抽卡找bug
  • 使用printf大法复现bug,python里多开线程测这个错误数据来提高爆率
  • 对于轮询bug,在while(true)里面使用printf大法定位,通过sleep来避免
  • 勇敢放弃de不出来的bug,选择有简洁美的随机

三、心得体会

1.线程安全

起初对线程安全只有一个模糊的概念,不就是对RequestTable上锁?后期迭代老师建议将电梯与电梯线程分离,但因为偷懒我一意孤行将电梯保持共商共建共享开放包容。因此我遇到了数不清的线程安全问题T_T

关于读写锁,我也只是在理论课和实验课代码中浅尝辄止,因为偷懒我一意孤行只使用synchronized,性能差差的很安心T_T

我想如果这单元能重来一遍,我一定会好好重构一次线程安全的优雅的代码,可惜没如果º·(˚ ˃̣̣̥᷄⌓˂̣̣̥᷅ )‧º·˚ ~

2.层次化设计

这单元的层次化设计非常清晰!基本架构沿用了三次作业

  • 输入线程、调度线程、电梯线程三板斧

  • 请求表、人员为共享资源

  • 电梯运行与策略分离

良好的层次化设计让我每次新增代码量不多(思考量不少

四、心情体会

  • 生活不止代码,高分值得我加倍付出努力,但它绝不是我生活的主旋律 (^∇^☆)

    美好的清明假期,无法割舍的是没有打成的明日方舟桌游,无法割舍的是凌晨的麦麦,无法割舍的是与同学海底捞小聚,无法割舍的是人大旁边的萨莉亚,无法割舍的是五道口的棋牌室,无法割舍的是朋友和稍纵即逝的青春(谁家6系人天天吃喝玩乐啊喂)

  • 看到这里的大家都注意身体健康!

    连续两周一坐坐一天对着密密麻麻数据debug,我能精准的记忆每位乘客不同调度策略的概率和我的bug出现概率

    夜昏乍似灯将灭,朝暗长疑镜未磨……我该换新眼镜(眼睛)了(ฅ´ω`ฅ)

  • 自我为难和放过自我之间的平衡

    对于不打算深耕cs专业的我,着了什么魔非要写影子电梯呢??可能是被追求卓越的大家感染了吧hhhh

    但我最后因为心得第一点和菜的原因,还是选择放过自我,随机真香啊suki!!!(ฅ´ω`ฅ)

  • 结尾致谢

    感谢亲亲舍友帮我找出来无数的bug

    cozy!“你一句你别急真不晚,我就到了真江南”
    Shae!267yyds!!存在即安心
    luma!睡神!noSleep_noSleep

    感谢耐心帮助、精神鼓励的良师益友们

    感谢讨论区以及开发测评机的无私奉献的佬们

    感谢OO课程组的倾情付出 给了我神奇的电梯月体验º·(˚ ˃̣̣̥᷄⌓˂̣̣̥᷅ )‧º·˚ ~