OO多线程初体验——我与电梯的故事

前言

oo课已经进行了八周,学习了多线程相关的知识,也终于亲自体验了传闻中的“电梯”编写。

在进行课程学习的总结之前,我想先对自己学习课程时的心情与态度做一次反省与总结。

其实一直到现在,在oo的学习过程中还是处于一种比较被动的状态,把作业都当作是任务去完成,交完作业过了中测就结束了,不会再去改进自己的程序。但上了几次讨论课后,就此发现了自己和大佬们的差距,这种差距既体现在技术水平的差距上,更体现在了学习态度上——这些同学竟然花了这么多课余时间去优化结构、改进算法、写评测机、学习新知识。

自己有时候会去抱怨课程制度或是种种,但无法提出更好的建议,这样不但不能改变自己的处境,而且带有这种情绪去学习还会降低学习效率。希望自己以后能改变自己的学习心态,向讨论课那些分享的同学们学习,技术上的问题是不能一蹴而就的,但是却可以向他们的学习精神靠拢。

丧是难免的,发泄也是需要的,但是沉湎其中是不可行的。
 

代码结构分析

1第一次作业

设计思路:

1一个Input类用于I/O接收

2一个调度器(ControlUnit)用于存储请求

3电梯Elevator用于模拟电梯运作

我的思路调度器接收请求并储存,电梯向调度器申请请求执行,执行完一条再申请下一条请求执行。结束时逻辑是,输入null时调度器中的endFlag被置1,若endFlag为1且请求队列为空就结束所有线程。逻辑简单,没有优化,但也不容易出错。

可以看出复杂度数据也并不是很高。

2第二次作业

设计思路:

在第一次作业的基础上增加了一个楼层类,目的是将总任务队列里的任务分类,方便电梯在某楼层停靠时实现捎带。

总体思路是没有变化的,采用ALS算法,无脑取第一个请求作为主任务,在执行主任务的过程中实现捎带,并完成主任务的动态变化。逻辑简单,不容易出错,缺点也很明显,不够优化

现在想来,最大的缺点在于:从第二次作业开始,我的调度器就失去了它的灵魂,因为我写的电梯比调度器更“智能”,这是很不应该的(这点也由我的复杂度可以看出,电梯承载了更多判断工作)。很多事情明明应该交给调度器去完成,比如说后来我觉得应该由调度器分发任务,但是我却是由电梯提出请求。这致使调度器的作用更像是任务仓库,而非调度器。

不过有一个改进,第一次我的调度器是自己new出来的,这一次我采用了饿汉式单例模式将调度器变成了唯一一个。

3第三次作业

设计思路:

较上一次作业增加了一个CheckRequest类,作用已被分配入电梯的任务是否能直达,能直达则返回自己,不能直达则将请求的目标楼层改变。

总体还是基于上一次作业的ALS,不同之处不过是用了一个01数组告诉电梯哪些楼层可以停靠哪些楼层不可以停靠。做法是电梯空闲则向调度器请求任务,有能够接到的任务不管是否能直达都去接,包括在每一层停靠时不管是否直达全都上电梯。之后的处理比较无脑,如果能直达就直达,不能直达,把不能直达的人全都扔到1楼或15楼(因为很惊喜地发现这两层楼所有电梯都会停靠)。在1楼和15楼只有能直达的电梯能接走申请人。

这种做法,性能很差,但我觉得依然是不容易出错的,并且逻辑非常简单,也很少需要进行判断,写起来比较简洁。缺点是可拓展性几乎没有,因为如果电梯没有公共楼层,这个方法就彻底无法使用。此外还是那个问题,调度器没有灵魂——它现在能做的智能工作也不过就是判断一下电梯是否能接到某楼层的人。

关于复杂性判断,调度器比较复杂是因为要判断是否能把请求给电梯。但是我想了一下,如果是调度器分发请求,这个复杂度依然会大,并且如果真的要让调度器进行大量判断,电梯可拓展性似乎也会减弱?也许我应该学习一下别人的代码……

说起来……其实是在这次的作业过程中,我发现了wait()原来是可以交出锁的……

最后,我在写这个作业的时候本来是有一个其他的想法的,就是分成四个请求队列,三个专属队列,一个公共队列,先有调度器将可直达任务分给专属队列,剩下的队列放在公共队列实现转乘,可是致使我放弃这种写法的是一个我不太理解的技术问题,怎么样在执行过程中锁住两个队列,但是又可以被其中任意一个队列唤醒呢?

程序BUG分析

1第一次作业

在写程序的阶段,因为线程安全问题出现了问题。在插入和读取的时候如果恰好撞在一起,就出现了取出一个空指针的问题。

2第二次作业

采用了比较简单的als(a little stupid)进行调度。

数据的存储采取了HashMap,用iterator进行数据的遍历,因为对其不太了解,导致出现了一些问题。简而言之,我想实现的是,遍历的时候想要按照插入顺序遍历,但是直接用HashMap取出的是随机的,所以导致当电梯没有任务向调度器请求第一个任务时,取到的任务不一定是最先提出请求的任务,而是随机给了一个任务。导致刚开始的任务有可能很久不能被处理。这个问题也导致了我第二次强测有个数据点超时了。

解决方法是换成LinkedHashMap

3第三次作业

这次作业因为完全没有考虑优化的事情,所以一切都在第二次作业的基础上修改而来,所以写完没有花多久,但是debug却用了很长时间(即使我的线程逻辑非常简单)。

首先遇到的问题是结束的问题,原来只有一部电梯的时候,我用的是System.exit(0)来结束程序的,我以为它的作用是结束单个线程,但是发现这个结束是结束所有线程,故修改用退出循环来结束线程。

其次一个问题,起初我用的方法是endflag(输入null置1)以及总队列中无任务结束,然后导致出现了某个接收了不可直达任务的电梯还没结束,即任务还没返还给总队列,但是以经被输入了null导致其他两个电梯线程被杀死的情况,所以又多加了一个结束条件——其他两个电梯都处于停止运行状态。

当成功解决了结束问题以后,虽然没有被评测机检查出新的bug,但是整理程序逻辑的时候还是发现了一些问题:

①电梯需要先输出乘客出电梯的语句,再把出电梯的乘客加入待调度总队列,否则会出现乘客还没下电梯,但是别的电梯已经接到乘客的情况。

②电梯应该先将乘客放下,再接新乘客,这样的话可以最大程度利用电梯容量。

 4其他问题

其实在写电梯(bug)的过程中,的确是对线程的理解渐渐加深了,但还是有残留困惑没有解决——即加锁的问题。对于同步语句的范围,我的理解还非常浅薄,还是比较喜欢直接同步方法,很少用同步代码块——synchronized(非this对象)的使用是把所有带有这个语句的代码块同步吗?并不影响未使用这个语句但访问这个对象的代码块?

心得体会

分析一下我自己为什么不像一些同学一样投入大量时间去做这些事情,以希求从根源上改变自己的态度。

1每次我开始写程序都是周末晚甚至周一了,并且拖延症导致我把其他很多作业都堆在类似的时间段内,因为时间的紧张,会让人丧失钻研的想法。

2自己对这方面兴趣不够,觉得写程序是件无趣的事情,自然会导致不愿意多看多写(这个其实有点无解)。

3生活太过安逸,渐渐堕落懒得思考。这个非常可怕,但的确自己有这样的趋势,继续改变。

此外对于多线程,我觉得经过三次作业渐渐有了自己的一些想法了,大多数情况也能理清思路,但是对于同步的方式依然理解不太到位,这也是我接下来几天会重点复习攻克的。此外,致使我放弃第三次作业本来想用的结构的问题我也还没有想出来,之后还会花点时间思考。至于其他一些在写代码过程中遇到的比较具体的知识点都已经在之前的分析过程中提出了,希望对看到这篇博客的同学有所帮助。

最后想感谢一下一些同学的无私帮助...愿意把自己写的测评工具公开实在是让人非常感恩。也希望有朝一日自己能写些东西帮助到大家。

原文地址:https://www.cnblogs.com/senbei/p/10743495.html