OO:UnitTwoTaskTwo
证明程序正确,1w条数据ac也不够;但是想证明程序错误,1条出锅的数据就足以。--HansBug
目录
前言
思路分享
debug_logger from HansBug
死锁:哲学家就餐问题
后记
前言
OO系列推送内容采用Markdown编辑,与OO的TimeLine保持一致:周六晚十点中测截止提交之后,群发推送。
欢迎各路神仙投稿,分享coding思路,设计模式等。
希望本篇内容能让各位看官满意。
思路分享
以下是笔者完成第二单元第二次作业的大致思路,已省略若干细节:)
1.大致思路
两种线程:主线程和电梯线程
主线程处理输入并将需求放置到相应的楼层类(详见第二部分)
电梯作为一个状态机按照调度器设置的信息运转(笔者并没有将调度器设置线程,因为笔者在第二次作业的实现过程中,发现处理输入的线程只要在新需求到来时调用一下调度器,然后调度器设置电梯的状态即可,所以并没有单独为调度器设置线程,减少了可能出现的线程安全问题)
2.优化&调度策略
①优化:楼层类
把每一楼层当成一个对象,共19个对象(19个对象同时复用一个楼层类),楼层类中包含一个请求队列,即19个对象包含19个请求队列且互不干扰
把主线程当成生产者,电梯当成消费者,楼层类当成托盘(单生产者多消费者模型)
有新需求时,主线程按照新需求的 from
楼层将其放入对应的楼层对象;电梯需要获取请求时访问与电梯当前所在楼层对应的楼层对象。
楼层类的好处:相比于把所有新请求放入同一个队列,楼层类可以保证不同楼层的请求队列能被不同电梯或生产者同时访问,增加效率,节约时间
19个楼层类和5个电梯实现的道理相同,都是在初始化时重复 new
一个相同的类(楼层类或电梯类),然后建立楼层(或类型)和对象的映射关系,之后的访问也是根据映射关系访问即可。
②调度策略:随机分配
既然电梯都是一样的,为什么不随机分配
当有新需求到来的时候,选择运行方向和新需求方向相同的电梯,通知此电梯有新的同方向的需求,此处只是通知而不是将需求和电梯绑定
需求最后由哪个电梯取走完全取决于几个同方向运行的电梯中哪个先到达需求的 from
楼层(类比现实生活,应该也是哪个电梯先到进入哪个电梯中)
因为不知道需求最后被哪个电梯取走,所以是随机分配
随机分配的关键是不过早的将需求和电梯绑定,直到需求进入电梯的子队列中,才确定需求由哪个电梯实现
随机分配的前提是几个电梯都是一样的,无论是电梯可以到达的楼层,还是楼层间运行的速度
随机分配的好处是当一个电梯到达一个楼层时,在不超过最大载荷数的前提下,能装多少就装多少,提升效率
当然电梯可能出现陪跑的现象,但大概率不增加总时长(因为当他发现同方向已经没有需求时(被别的电梯装走了),他就会掉头或等待)
(当然随机分配只是笔者采用的策略,不一定最优,但相比于笔者在采取随机分配之前自己实现的各个调度策略,随机分配能使效率得到很大的提升)
3.关于线程安全bug本地无法复现的问题
笔者只是提供一个复现bug的思路,至于采用此思路之后能否复现笔者也无法保证
当出现线程安全问题(比如你的程序停不下来),可以考虑将运动时间和开关门时间减小到原来的1/10,并且将投放时间间隔缩短到原来的1/10,然后对拍
如果拍出来了(指复现了),那恭喜你可以继续debug了
如果还是没有拍出来,那就再多拍几次
如果还是没有拍出来,那笔者也没有其他的想法(毕竟学校的评测机性能极佳,本地的机器无法比拟)
debug结束之后别忘了将运动时间和开关门时间改为原来的时间
4.设计中的思考
物尽其能:
比如将人的行为还给人。上下电梯时的输出,封装在人的类里,人需要上下电梯时,直接调用方法即可
传递的是对象:
比如调度器需要获取电梯的若干信息,可以将这些信息封装成类,这样调度器获取时,只需要return new State(...)即可
debug_logger from HansBug
HansBug原博客:
https://www.cnblogs.com/HansBug/p/8701447.html
PS:如果直接在github上clone,请记得将 DebugHelper.java
类中的 dPrintln
改成 debugPrintln
,当然如果不改的话,在执行 test
的 main
时也会报错的
笔者两周的实际使用感受:这是一个可以自行决定debug信息的颗粒度,自带logger输出,输出信息丰富、便于拆除、提升调试效率的轮子。
debug_logger的使用:
1.在程序中部署需要输出的调试信息
先在程序的开始初始化 DebugHelper
的参数
import com.hansbug.debug.DebugHelper;
public abstract classMain{
public static void main(String[] args) {
DebugHelper.setSettingsFromArguments(args);
...
}
}
再在程序中部署 debug_logger
的核心API:
DebugHelper.debugPrintln(level, "message to output")
level分为1,2,3,4,5:分别对应不同的输出粒度
笔者设置的粒度(读者可自行调整)
level 1:输出线程的开启结束信息(一般部署在线程的开启和结束位置)
level 2:输出方法的调用信息(一般部署在方法的前后)
level 3:几乎每隔几行输出相应的调试信息(一般部署在逻辑较复杂的区域)
...
(笔者目前只用到level3,后续迭代任务有更复杂的要求时再继续部署更深层次的level)
2.调用命令行运行程序并传递参数
接下来展示的是在作业一中只在 Input
线程部署上述 API
的输出结果(线程的开启和结束信息设置成level1,新的输入设置成level2)
Input : 1-FROM-1-TO-15
以下为debug的输出日志:
图1
命令行具体输入的参数包括:
-D , --debug define the debug level. The maximum of the debug level is
5
and the minimum is1
.--debug_package_name define the limit of the package name(full name, just like
com.hansbug.debug
)--debug_file_name define the limit of the file name(short file name, just like
Main.java
,DebugHelper.java
)--debugclassname define the limit of the class name(short class name, including inner classes, like
TestClassA
,TestInnerClassA
(instead ofTestClassA.TestInnerClassA
))--debug_method_name define the limit of the method name(just like
toString
(constructor method))--debugincludechildren define whether record the log in subroutines that called by the method in the limit above.
--debugshowthread define whether show the name of threads which execute the
debugPrintln
.
当然你也可以设置参数、直接在idea里运行
在idea里直接设置参数:
3.拆除logger
文本编辑器全局搜索 DebugHelper
并删除所在行即可
使用debug_logger的好处
如果程序执行异常,能快速准确的定位(参数中设置
--debug_show_location
)能够自行决定颗粒度且能根据逻辑架构由浅入深、由略到详自行部署
输出内容丰富(比如位置、时间戳等)且程序执行之后只需分析log
无需使用断点调试多线程程序、操作流畅
便于拆除(最后别忘了拆)
死锁:哲学家就餐问题
问题描述:
假设有五位哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考。吃东西的时候,他们就停止思考,思考的时候也停止吃东西。餐桌中间有一大碗米饭(真正的哲学家只吃饭不吃菜(大雾)),每位哲学家之间各有一根筷子。他们只能使用自己左右手边的那两根筷子。哲学家从来不交谈,这就很危险,可能产生死锁,每个哲学家都拿着左手的筷子,永远都在等右边的筷子(或者相反)。
大致情况如下图:
即使没有死锁,也有可能发生资源耗尽。例如,假设规定当哲学家等待另一根筷子超过五分钟后就放下自己手里的那一只筷子,并且再等五分钟后进行下一次尝试。这个策略消除了死锁(系统总会进入到下一个状态),但仍然有可能发生“活锁”。如果五位哲学家在完全相同的时刻进入餐厅,并同时拿起左边的筷子,那么这些哲学家就会等待五分钟,同时放下手中的筷子,再等五分钟,又同时拿起这些筷子。
问题抽象:
现在存在五个进程,问题的关键在于如何让一个哲学家拿到左右两根筷子而不造成死锁或者饥饿现象。
解法:
解法1:筷子凑成对,同时拿到一双筷子
把筷子凑成对,让要吃的人先吃,没筷子的人得到一张换筷子券
等到有筷子的人吃饱了会把一双筷子交个有券的人,已经有券的人不会再拿到第二张券
保证有筷子的人都有的吃
解法2:服务生解法
餐厅引入一个服务生,服务生知道所有筷子的使用情况,哲学家必须经过他的同意才能拿起筷子。
......
(当然还存在其他的解法,上述两解法笔者觉得实现起来较为容易,所以介绍给各位)
后记
希望各位神仙们积极投稿(笔者联系方式在公众号菜单栏放映室中)
如果各位看官觉得本篇推送小有裨益,欢迎订阅本公众号:十五号放映室