OO第二单元博客作业

yplqing 学生 2024-04-19 20:42:34

hw5

电梯运行策略(LOOK策略)

尝试放客

确定主方向

判断是否需要停止

尝试接客

关门

移动

确定主方向

若到了两端,直接设定主方向;

若有内部请求,则主方向不需更新,跳到下一步接客;

(以下没有内部请求)

否则,若没有能接的外部请求,若没有结束就wait(),否则false;// 实现:遍历,用flag记录是下面的ABC哪种情况

(以下有外部请求)

否则,若有 来自主方向、不来自本层的外部请求,则不变主方向;// A

(以下外部请求【来自主方向但来自本层 或 来自反方向】)

否则,若来自本层的外部请求的方向去往主方向,则不变主方向;// B

(以下全是来自反方向的外部请求)

否则,转向 // C

正优化

1.开门与关门之间间隔的时间应接尽接——实现:在关门前查询一次即可

对应的样例

[0.0]1-FROM-1-TO-2-BY-1
[0.4]2-FROM-1-TO-2-BY-1

2.(量子电梯)关门后与到达下一层之间间隔中,若有人则“回来”,重复步骤1——实现:在到达前查询一遍,重复步骤1即可

关键代码:

requestPool.wait(tmp);

对应样例

[0.0]1-FROM-1-TO-2-BY-1
[0.5]2-FROM-1-TO-2-BY-1
[0.8]3-FROM-1-TO-2-BY-1

hw6

电梯运行策略(加粗为新增)

尝试放客

检查是否需要重置

检查是否需要计算

尝试接客

确定主方向

检查——需要重置或计算就 continue

判断是否需要停止

检查——需要重置或计算就 continue

尝试接客

检查——需要重置或计算就 continue

关门

检查——需要重置或计算就 continue

移动

说明

此次作业新增 receive 输出要求,需要调度策略,我采用了影子电梯的方法。上述的“计算”指电梯检查到“需要计算的请求”不为空时,立刻调用cal()函数,计算出此电梯完成所有分配给它的请求的时间,然后通知控制器,接着进入等待,等待控制器接收到这个算出来的时间后通知自己。由上述电梯运行策略可知,电梯的某些操作似乎是“原子性”的,不能进入计算状态,导致控制器等待,我的解决方法是在某些必要操作中嵌入“是否需要计算”的判断,导致函数冗长。更好的解决方法是改写成状态机实现。

对reset的处理

请求池中新增cnt变量记录包括reset请求的数量,cnt为0才通知电梯线程停止。

reset前被迫出电梯的人变成新请求,为了防止互锁问题,使用新线程加入请求。

由于在reset过程中需要计算影子耗时,需提前将“上次到达时间”置为重置结束时间

对新请求的调度

将此请求的 usedForCal 变量置为真,表示此请求是出于计算目的临时进入请求池中;依次将各电梯“计算状态位”置为真,进入等待,等待电梯通知;比较时间,得出 bestId ,将此请求的 id 置为 bestId,usedForCal 变量置为假,唤醒所有电梯。

电梯及时反应控制器的措施

为了及时计算出耗时,电梯在关门、量子移动、重置过程中都要保持对锁的监视,以便于通信,所以我采用了 将某个对象作为锁 + 标志位 的方法。

比如在reset的1.2s中,双重判定——等待前判定、拿到锁后再判定一次

while ((tmp = gapForReset()) > 0) {
        cal();
        synchronized (Lock.getLockForNewRequest(id)) { // 锁
            try {
                if (needToCal) { continue; } // 标志位
                Lock.getLockForNewRequest(id).wait(tmp);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
}

这个方法其实有明显问题,因为还是会造成线程唤不醒的情况,但概率很低

hw7

双轿厢电梯的影响

电梯的“分裂”

我的处理方法是直接开12个电梯线程,id分别为1~12,1~6 对应 1-A、2-A、……、6-A,7-12 对应 1-B、2-B、……、6-B,初始化时,所有A电梯的楼层为0,B电梯的楼层为1,换乘楼层都为1,也就是一开始都是B电梯在工作。

另外,对于A的启动,我选择在B进行双轿厢电梯重置时启动A,具体来说B存着A的指针,直接更改A的楼层和换乘楼层即可。

由于A和B不能同时在换乘楼层,A中也要有B的指针,便于它们互相获取对方的当前楼层。我选择了双向通信处理

新请求的调度

为减少工作量,不考虑不同id电梯间的合作;

具体过程如下:

遍历电梯id(1~6),若上方的B或者下方的A能单独完成,就返回B或A的时间;

否则,如果从下到上,计算A类完成将此请求放入换乘楼层和其他任务的总时间 t1、将此请求放入换乘楼层的时间 t2 (两个时间);另外计算B正常移动到达换乘楼层并接走此请求的时间 t4 ,和完成包括这个请求的所有任务的时间 t3——t2小于t4说明B能顺便接走请求,两者互不影响,返回 t1和 t3 的较大值;否则,返回 t1 和 (t3 + t2 - t4) 的最大值;

如果从上到下,计算B类完成将此请求放入换乘楼层和其他任务的总时间 t1、将此请求放入换乘楼层的时间 t2 (两个时间);另外计算A正常移动到达换乘楼层并接走此请求的时间 t4 ,和完成包括这个请求的所有任务的时间 t3——t2小于t4说明A能顺便接走请求,两者互不影响,返回 t1和 t3 的较大值;否则,返回 t1 和 (t3 + t2 - t4) 的最大值;

根据时间选出bestId即可

其余更改

放客要考虑换乘乘客的释放

整体分析

同步块的设置和锁的选择

由于没有使用读写锁,只使用了 synchronized ,必须在读和写共享对象时给对象上锁,同步块仅包含有关此对象的部分,提高了效率。

线程交互

为了方便,我定义了一个锁类,包含各种锁,充当交互的媒介,比如当控制器想唤醒电梯1时,电梯1此时肯定在等待”锁1“,控制器使用”锁1.notify()“就能唤醒电梯1,其他线程同理

调度策略

主要考虑影子电梯计算出的时间,时间相同就考虑使用双轿厢电梯;还能优化的方向:当电梯离开某一楼层时,若该楼层还有乘客,就将该楼层的所有乘客重新分配!

架构设计

hw5没有使用调度器,采用”输入-请求池-电梯“的架构,核心是电梯在请求池里找请求而运行;hw6、hw7使用了调度器,采用”输入-控制器,控制器-请求池,控制器-电梯,请求池-电梯“的架构,即控制器接收输入,控制器负责请求的加减,控制器负责电梯的LOOK策略,请求池能通知电梯。

协作图

img

img

三次作业稳定的内容和易变的内容

稳定的内容:整体架构(输入-调度器-电梯)、电梯运行策略(LOOK策略)

易变的内容:电梯性能参数、电梯可以完成的请求

实现双轿厢的两个轿厢不碰撞

在电梯关门后,等待若干时间即将输出到达信息时,进行判断,若兄弟电梯在换乘楼层,则通知兄弟电梯,然后自己进入等待,被唤醒后重复判断(因为可能是被控制器唤醒,需要计算耗时);由此可将换乘楼层视为一个锁,或者说将移动视为一个互斥过程,将双轿厢电梯设置为不能“同时”进行移动,因为可能同时移动到换乘楼层。

出现过的bug和debug方法

我出现过的 bug 是死锁和 “死等”(就是由于双向通信的疏忽,导致某进程永远无法被唤醒),debug 方法是多次运行出错样例,复现后,使用 jps 和 jstack 查看各个线程的状态就能很容易看出来。

心得体会

线程安全:线程安全需要线程以正确顺序执行,意味着需要线程正确通信,正确的运行、阻塞和就绪,这个单元对我的挑战主要是如何实现”自治的线程及时向外传递信息“,现在看来用类似状态机的思想处理会好处理一些,虽然没有实现。

层次化设计:当某个类掌管的数据和方法过多,使用 synchronized 很容易互锁时,代表着这个类跨越层次了,功能冗杂,此时应该将这个类的一部分数据和方法抽象出来,成为新的类。

...全文
47 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

301

社区成员

发帖
与我相关
我的任务
社区描述
2023年北航面向对象设计与构造
学习 高校
社区管理员
  • YannaZhang
  • CajZella
  • C_ecelia
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧