一、多线程电梯
1、类图与流程图
2、设计分析
此次作业中,出Main线程之外,我使用了一个调度器线程以及三个电梯线程。
调度器和电梯各自维护一个请求队列,代表需要执行的请求。
Main线程负责从控制台接收请求,进行输入处理,并将合法的请求加入调度器线程中的请求队列;
调度器线程从请求队列中一条一条地取出指令,并根据电梯的状态分配指令,即将指令加入到各个电梯的请求队列中;
电梯线程从自己的请求队列中取出指令,执行指令并输出相关信息。
线程安全问题主要出现在对于各个线程的请求队列的维护上。不同的线程对一个请求队列进行添加,删除操作,可能造成许多问题。应当使用线程安全的措施保障对请求队列的各种操作。现在回想起来可以使用BlockingQueue简化代码。
3、Bug分析
此次作业公测部分没有出现问题。但是在互测过程中出现了几个BUG。
首先是readme的问题。同学认为对于一行多个请求结尾是一个分号的情况下应当进行空指令报错。我认为分号后没有指令并不应当进行空指令报错,经过助教仲裁后决定这应当必须在readme中定义,故算作bug;
其次是电梯捎带问题。对于同一时刻输入的(FR,3,UP);(FR,2,UP);(FR,1,UP)这三条指令,我的程序中将其判断为由一台电梯捎带,但是互测的同学认为这样违背了电梯运动量均衡原则,所以应当由不同的电梯执行;
最后是输出时间的问题。由于时间取自系统时间,所以时间间隔可能不是恰好的3s、6s。而且在程序运行过程中,随着指令输入的增多,时间误差会越来越大,对于这个问题,我目前仍没有想到一个万无一失的方法能够解决这个问题,还需要继续学习探索。
二、IFTTT
1、类图、度量分析与流程图
2、设计分析
此次作业要求实现一个文件监视系统。我的核心思路就是对于每一个需要监控的对象,对于其每一种触发器新建一个线程进行监视,如果满足监视停止的条件那么线程结束。
Main线程中主要负责输入处理以及新建线程;
对于每个触发器线程,在新建时进行初始信息的存储,随后以2s的周期扫描监视的文件目标,如果满足触发条件,执行相关动作;
测试线程用于对文件进行改动。
此次作业的线程安全问题主要出现在Detail和Summary向文件中写入的时候。可能有多个触发器线程同时需要向文件中输出相关信息,所以应当将输出文件的方法加锁。
从度量分析中可以看出,此次作业的Main线程写的比较仓促,将输入处理以及各种初始化操作都直接写在Main中,导致Main线程比较臃肿。同样的问题还发生在四个触发器线程中,在run方法中加入了过多的逻辑判断,导致代码可维护性差,需要在今后的作业中努力改善。
3、Bug分析
此次作业公测中,监控目录并且挂载change-path触发器时,要求输出具体的文件路径信息。我参考了issue中助教对于目录detail的说法,即监控目录时可以只输出size信息,与公测要求相悖,错了两个点。不过并无大碍,仅仅是一句输出语句的问题,程序的思路上没有什么大的问题。
三、模拟出租车调度
1、类图、度量分析与流程图
2、设计分析
此次作业要求实现一个模拟出租车调度系统。我的核心思路是首先启动100个出租车线程,代表100辆出租车,随后对于每一个输入的有效指令,新建一个3s的调度线程,查找满足要求的出租车,并分配指令。
Main线程从控制台读入叫车请求,建立调度器线程;
调度器线程读取出租车状态并且选择最优的出租车分配请求,记录信息并输出到文件中;
出租车线程模拟出租车的运行,执行请求,记录信息并输出到文件中。
线程安全问题可能出现在两个方面。首先,一辆出租车可以抢多个单,也就是说可能有多个请求的调度器要读取甚至改变一辆出租车的状态,那么同一时刻只能允许一个调度器线程对出租车的状态进行改变,对于出租车的指令分配需要加锁。其次可能有多个线程同时向文件中输出信息,也需要加锁。
通过度量分析可以看出,此次作业在中调度器线程的run方法还是写的有些臃肿,模块化程度不高,还有提高的余地。
3、Bug分析
此次作业公测和互测均没有发现Bug。互测过程中,同学的代码完成度较差,问题较多,在这里就不一一列举了。
四、心得体会
通过5、6、7次作业,我对于多线程编程有了初步的了解,也学会了解决线程安全问题的一些方法。但是总的来说这些方法思路还是过于繁琐,还有更多更加灵活,更加简便的保障线程安全的方法,而且在开始编程之前一定要仔细思考各个对象,各个线程应当完成的工作是什么,越详细越明确越好,最好能从头至尾遵循一个大的原则,这样能避免编程过程中许许多多的问题,节省大量的时间,并且保证程序的可维护性。
互测过程中,发现Bug的首要方式应当是阅读代码。只有阅读代码才能发现对方程序中所有的问题。但是实际情况中,互测开始到结束的时间说短不短,说长也不长,在完成正常课程的同时阅读源代码,搞懂对方的思路实属不易。特别是我们现阶段没有要求写注释,面对数百上千行的代码,阅读起来确实有些力不从心。不管怎么说,原则上来说,阅读代码是必须要做的。除此之外,应当准备边界数据,比如数字大小,规模边界的测试样例、还有特定时间的测试样例、可能有线程安全问题的测试样例,进行测试,也能够发现一些问题。
对于线程安全问题,我的原则是首先完成基础功能,再去思考多线程交互问题。也就是说,首先应当思考如何完成程序最基本的功能,将最基本功能实现之后,再去思考线程安全问题。判断线程安全问题应当将每个线程需要读、写的属性列出来,若Bernstein条件不满足,那么就存在线程安全问题。最通常的解决办法就是加锁,但是盲目加锁会造成多线程的执行效率下降,甚至与单线程无异。所以加锁的范围要明确,要尽可能小。还要学会借助封装好的工具来解决线程安全问题,如BlockingQueue等等。
我们之中有许多大牛,代码美观简洁、功能完善,我的水平还差得很远。但学习是个循序渐进的过程,如果每次作业都能有所收获,大概也不虚此行吧。