多线程

Posted on 2021-04-26 22:01  O(0o0)O  阅读(64)  评论(0)    收藏  举报

JAVA 多线程操作

  这一个单元是关于多线程操作的,第一次作业的要求是有一个请求的输入列,相应的控制一部电梯,及两个线程,一个输入线程,一个电梯的运行线程。第二次作业则是将电梯的个数增加到了3个,另外再加两个可被启动的电梯。第三次的话电梯的数量没变,只是将电梯的种类从一种变成了三种。

  这三次作业属第一次作业难度最大,后来的两次小改即可。

作业中的同步块与锁

  第一次作业,由于当时理论课上摸鱼,久久没动笔,直到上完上机课,我才开始写。上机的实验给了一个很好的例子,它相比于第一次作业多了一个调度器,也就是说多了一个线程。其实现在回想起来,调度器就这三次作业而言并不是不可或缺的,但是它方便了编程。第一次作业我也是采用的生产者消费者模式,有了两个队列,即两个共享对象。两个队列分别为 Requestqueue 和 Processqueue 。

  • Requestqueue

    这个对象被输入线程和调度线程所共享,在输入线程中,它的同步块如下:

  

 

    当输入结束时,通知所有在等候Requestqueue的线程,将锁释放出去。否则输入一个请求,Requestqueue增加一个请求。

    而在调度线程中,其同步块如下:

 

 

     整个调度函数都是在一个同步块中,因为如果一旦有一个请求被输入,那他立即会被调度线程调度到电梯的处理序列序列Processqueue中。故在该线程中同步块中还有同步块:

  •  Processqueue

 

 

     在电梯的运行中,同步块如下:

  

 

 

    这是一个电梯开门的进人处理过程,锁住Processqueue之后才能对其进行改变。

    即:总共三个线程,输入线程和调度线程共享Requestqueue,调度线程和电梯运行线程共享Processqueue。三者是共同运行的平行关系,自由竞争共享线程的锁。我的后面两次作业还是这个基本架构。

调度器设计

  我设计的调度器为了应对三种模式运用了一个switch语句。对于三个不同的模式,我采用了三种不同的调度策略(但是后面实践发现没有这个必要,因为我还在电梯里采用了三个不同的运行模式。。。)三次作业中我的调度模式都是相似的。

  •  Morning 

  依据题意可知,乘客只会从第一层出发,所以我的调度策略是每当电梯塞满了(人数达到六个人了),才释放Processqueue的锁,让电梯不再waiting。至于题目中所说的等候时间不超过两秒,我也问了周围的同学和猪脚鸽鸽,感觉没啥好的处理方法就忽视了。

  第二次作业则是平均分配,多部电梯按顺序接满六个人就走。第三次作业则是按照目的楼层匹配电梯,在满足条件的情况下按照电梯速度的快慢先后顺序进行分配。

  •  Night 

  依题可知,乘客会在某一时刻全部到达,目标楼层都是第一层,则调度器在全部调度完成后再通知电梯运行。

  第二次作业是平均分配,不过是来一个分配一部电梯,循环分配。第三次作业则是按照起始楼层匹配电梯,在满足条件的情况下按照电梯速度的快慢先后顺序进行分配。

  •  Random 

  这个我的调度策略是来人就分配,即按照时间的先后顺序分配的。

  第二次作业的话是随机分配的,这个我尝试过扫描每个电梯里的人数优先分配给人少的电梯,但是由于我的电梯是可捎带的,故对未来的预判准确度不高,所谓人少的电梯到达的时候人数就不一定少了,故最终采用随机大法。第三次作业则一样是在满足电梯的运行条件下尽量让速度快的电梯运人。

  调度器是整个程序中交互最多的,它既要和输入线程交互,也要和一个或多个电梯线程交互。与输入交互,是通过Requestqueue这个共享对象进行的。而对于电梯的交互,则是通过Processqueue这个共享变量来实现的。

功能与性能

  UML类图

 

 

   这是第三次的UML图,另外两次类似。其实没有必要使用Pqs(Processqueue )和Elvs(电梯的集合),但这样可以方便之后的扩展,如果有大量的电梯的话可以很方便地进行管理,这也算是为之后的扩展做准备。

  UML协作图:

 

  这个架构比较适合扩展,相当于生产者消费者中的两个传送带,也不容易引发线程安全问题。

自己的BUG

  主要问题是在我的第一次作业的时候,出现了一个死锁的bug。原因很简单,我照抄了上机课给的代码。在我的调度线程里面,我先锁的Requestqueue再锁的Processqueue。而在我的电梯线程里面,我先锁的Processqueue再锁的Requestqueue。这样一来当二者都得到第一层锁之后就没法继续进行下去了。

  这个解决方法横简单,我直接把电梯里的第二层锁给他去掉了。因为在电梯里我只是为了判断输入有没有结束而使用的锁,其实判断结束了根本不需要锁。

他人的BUG

  这个单元我没有hack任何人,一是构造针对性的数据限制较大,二是对别人的输出结果进行正确性判断不大容易。

  我觉得好一点的方法就是重点读一读别人代码设置锁和释放锁的地方,从逻辑上判断有没有死锁的可能,有没有无法唤醒的可能。

心得体会

  这次作业最初我完成的比较困难,不该上课没认真听讲。而后后面的作业我也只是小改一下,没有做大胆的尝试,对自己的代码一点都不满意。对于lock,原子对象等新方法内有试试,感觉很是遗憾,而且我的电梯运行方法也十分面向过程,就很失望。很后面的两次作业我可以说毫无收获,哎,太摸了。特别是我的第三次作业,我甚至连风格都忘记改了。但愿我会找个时间重新补一下这个单元我该学会的。

  收获还是有的,第一次接触了多线程编程,对线程的安全啊啥的概念有了一定的了解。具体的话有以下几点印象深刻。

  wait、notify和notifyAll操作必须在同步方法或者同步块中,否则会报IllegalMonitorStateException:current thread not owner。

  wait和sleep的区别是wait会释放对象锁,别的对象可以调用它的synchronized方法,调用这个对象的notify操作会使这个线程重新进入runnable状态。

  synchronized分为同步方法和同步块,用来处理多线程环境下的数据同步。使用不同的对象锁,在性能上会有所改善;同时synchronized不属于方法签名。  

  不过多线程的bug感觉还是比较难搞的,主要是那种隐性bug,他要很多次运行才能出一次错,所以还是得逻辑分析自己的代码。。。

  还有关于调度策略,我只采用了自己琢磨的策略,和同学交流发现大家的都差不太多。就是在运行途中能捎带就捎带(请求的方向一致),到达目的地后如果电梯内有之前这个方向上的乘客,选出最远的,设为目的地运行,否则反向选出最远的作为目的地运行,如果没有乘客,则选择最近的请求去接送乘客,这样尽量减少电梯的折返。但由于未来的不可预测性,所有策略的效率都依靠输入数据,只能说找最有可能的情况应对。感觉这就和我们的生活很贴近了了哈哈哈。

博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3