OO第二单元总结

OO第二单元总结

一、单元重点

1.1 多线程的概念和理解

多线程,顾名思义,就是在一个程序运行时有多个同时运行的线程,它是比进程更加细化的概念,主要的针对的研究对象就是我们的共享对象,针对共享对象进行各个线程间的合作协同,一般情况下能较大程度提升程序性能。

1.2 JAVA多线程

  • JAVA语言自身就是支持多线程的:最简单的实现方式就是用sychronized关键字,能够实现对一个类或者方法进行加锁,当多个不同的线程运行到这一段代码时候,保证同一时间只有一个线程在运行这一段代码。当然除了使用我们的sychronized关键字来进行同步,还有很多其他的像用ReentrantLock类来进行同步块控制,相较于前面的sychronized关键字,这个会更加的灵活,就比如可以实现设置控制线程如果一段时间内没有拿到锁的话就去执行一些其他的处理,不用一直在那里等着,可以一定程度上避免了死锁的产生。

1.3 同步块和锁

  • 锁的选择:在三次作业中我都是使用了sychronied关键字进行的加锁同步,主要是自己没有弄明白读写锁的使用,怕自己这样贸然的在作业中使用的话会报出一堆BUG,那就有些得不偿失了。并且我在搜索学习中了解到jvm现在对于我们sychronied关键字的优化已经很高了,性能也不会很差劲。
  • 同步块设置:
    • 第一个是在进行共享的一个请求队列的里面的所有涉及到写操作的都进行了加锁,开始还是没有太弄明白这个加锁的意义,所以对这个类中的所有的方法加了锁,在后面的学习中意识到这个类的读操作时没有必要进行加锁的,这个不会有线程安全问题,反而会降低程序的性能。
    • 在一个就是我们要自己封装一个线程安全的输出类,否则就会在互测中被hack出来十多个bug(bushi)
    • 还有就是在电梯类里面要进行一些方法操作的加锁,这个在第一次作业中没有关系,因为在第一次作业中每一个楼座只有一个电梯运行,一个楼座一个请求队列,不会出现多个电梯线程操作一个队列的情况。之后在每一个楼座和楼层都有不止一个电梯,是要在电梯类里面进行方法的加锁。保证程序的正常进行。

二、调度器设计

在整个的三个作业中,说实话在前两次作业中都不算真正的有调度器,顶多算是一个分发器,职能和作用是去将请求合理的“分发”给每一个电梯等待序列,而到了第三次作业中,由于要对于请求进行识别和分类,将要进行换乘的“请求”根据现在所在的楼层和楼座调度到相应的电梯请求队列,才真正算是拥有了一个调度器。

而实现电梯运行的方向和是否开关门的请求和操作实在电梯内部实现的,整体是采用一种LOOK策略,其实在最开始的第一次作业中采用的是一种SSTF策略,本来也没啥,也是一种可行的调度方式,但是在第一次互测阶段有同学认真读了我的代码,看出来我用的是SSTF策略,然后构造了相应的针对性数据,卡出来了RTLE,然后我修复BUG的时候发现这些的数据我的程序就是过不去,自己分析后发现是自己的策略在处理这个数据就是会被卡,于是决定将自己的调度策略改成LOOK策略,之后性能上就没有出现过很严重的问题(虽然强测中还是会出现一些85的点)。

在楼座和楼层的内部都是采用的自由竞争,本来看指导书的分配请求建议还想着如何去实现一个较为完善的分配方式和策略,但是看了学长和学姐的相关博客,加上又和同学进行了一些讨论,决定使用自由竞争策略,也确实没有产生什么很严重的问题。

然后具体线程间的交互是InputThread类来添加我们的电梯线程和调度器线程,还有的交互就是通过两个生产者-消费者模式来进行交互。一个是输入线程和调度器线程之间,一个是调度器和电梯线程之间。

三、结合线程协同的架构模式,分析和总结自己

  • 三次作业架构设计的逐步变化和未来扩展能力画UML类图

架构模式:

这个架构模式可以说三次作业都是一种生产者-消费者模式。三次作业变化较小,一定程度上可以说明程序的扩展性可以。一共是输入线程、调度器(分发器)线程和电梯线程,组成了两个生产者-消费者模式。一个是以输入线程为生产者,调度器作为消费者,而其中用一个共享的请求队列作为中间的“托盘”。然后调度器转过来在作为一个新的生产者,将指令进行识别和一定的处理后放入每个楼座楼层的新待处理请求队列的“托盘”,每个楼座楼层的电梯用自由竞争的方式来“消费”这些请求,并且以LOOK的策略处理请求。这就是我的电梯作业一个整体的架构和运行处理逻辑。

只不过在第三次作业中我们要处理带有换乘需求的请求,所以增加了流水线模式,不过说实话我认为我并没有真正的实现了流水线,只不过是在课程组一些对于用户请求近乎严苛的要求下完成的一种可以处理固定换乘方式的电梯。调度器具体的实现方式就是根据这个请求所在的楼层和楼座识别出来这个请求在处理的第几个阶段,然后放进到相应的待处理请求队列就行。

作业实现:

UNL类图:

前面两次作业的家都实现基本是一致的,生产者-消费者模式。下面是第二次作业的类图。

最后一次作业中我们电梯会在最后进行一个请求是否到达想去的终点的判断,如果不是,电梯会成为一个新的“输入线程”,向调度器重新“投入”这个请求。下面是UML类图。

UML协作图:

协作关系三次作业基本是一样的,主要的不同就是我们第三次多了一个电梯作为一个可能的生产者再次和调度器协作。下面以第三次作业为例:

四、作业问题

  • 在第一次作业最开始的时候还是自己没有真的去理解多线程的工作原理,脑海中也没有一个关于这次作业的整体的架构,导致可以说真的算是等到实验结束才开始动笔写,借助于实验的代码进行改动才写好第一次作业。
  • 借助实验代码整体实现不是很难,最纠结的还是调度策略的选择,这个花的时间最长了,最后还是决定去使用一个我认为较为容易实现且效果不错的SSTF策略。
  • 然后再第一次作业的强测和互测中出现几个RTLE,上面也说过了,就是自己的SSTF的调度策略太过于的垃圾了,在BUG修复阶段改成了LOOK策略才过,之后的两次作业也没有在改过策略,也没有再出现RTLE的点了。
  • 第一次作业中除了因为调度策略被hack的,还有就是自己输出线程不安全,没有进行任何的包装,也被hack了不少点。
  • 第二三次作业的强测和互测没有出现什么BUG,就是有些强测点的分数确实惨不忍睹。
  • 再有就是自己在完成第三次作业的时候出现了一次无法结束线程和CTLE的情况,就是原本我们的Schedule线程将所有指令给到电梯后就可以结束了,但是这样在第三次作业中我们还可能结束后又有需要处理换乘的请求被电梯再次投入waitQueue,这样就不行了。再加上原来Schedule线程在发现waitQueue空了之后是进行continue操作(继承实验的做法),算是一种轮询,但是前面两次作业都么有出现CPU爆了的情况,可在第三次里中测的十个点一个过不去。后面我采用wait和notifyAll的方式解决了CTLE,用一个显示当前处理完的指令数来作为是否能够停下Schedule解决了线程无法结束的问题,成功过关。

五、心得体会

  • 还是要适时的进行预习工作,就像第一单元突然写之前从未写过的JAVA程序,这个单元写之前没有写过的多线程,真的要在进行一个新的单元的时候早点预习这个单元的知识,不能够等着作业下来后才开始都学习,真的会在开始占用很多完成作业的时间,导致作业完成的十分的紧张。
  • 这个单元自己还学到的就是在自己没有思路和遇到困难的时候可以选择去看看博客,去学习学长学姐留下的一些经验和教训,真的可以避免很多不该出现的错误。
  • 设计架构的扩展性真的很重要,这次可能因为自己的架构还是可以接受的,在完成第一次作业完成的基础上,第二次作业只花了一天不到就完成了,第三次作业基本沿袭原来的架构,也是很快就完成了,分数也能够接受,这个单元真正体会到一个好架构的好处。
  • 最后也感叹一下,终于结束了这个“名声在外”的电梯月,再加上一些其它的事情,这个月真的可以说是死亡月,连续很多天都是两点到八九点的作息。不过不管怎么说都算是过来了,不论是在OO、其他课程还是些其他的方面,也小小的肯定自己一下,继续加油!期待在计院后面的学习和经历。
posted @ 2022-05-04 15:19  谷福胜  阅读(23)  评论(1编辑  收藏  举报