单元小结


 (删不掉了。。。)



又到了单元小结的时间。

新的三次作业分别为多线程电梯调度、IFTTT、出租车调度

下面是我在实际实现的过程中所遇到的困难与挑战。


一、多线程调度

第一次接触多线程作业的我是一脸懵逼的,什么是多线程,什么是线程安全,什么是锁,怎样加锁等等,这些问题迫使我去阅读相关的博客来消化这些概念。首先上一张多线程线程状态图:

图中提到的各种方法按照调用方式大致可以分为以下几种

线程方法:sleep、join、yeild等;

对象锁方法:wait、notify、notifyAll;

同步手段:synchronized

线程方法的调用主要和系统资源分配相关,如sleep方法使线程睡眠,让出系统资源,从而使得其他线程有更多的被执行机会。对象锁方法主要与线程安全相关,保证当多个线程访问共享对象时执行结果与线程的先后访问顺序无关。其中,sleep与wait方法的区别是sleep方法并不释放对象锁,wait方法释放对象锁并进入该对象的“等待池”中等待被唤醒。有关对象锁的解释有一篇博文讲的非常形象,具体记不太清是哪个了。。。不过大致思想如下。

我们可以将实例化的对象想象成一座公寓楼,对象方法是公寓楼中的房间,线程想象成访问公寓楼的人。每座公寓楼大门有一把唯一的锁,所有房间共用这唯一的一把锁。一旦某个人拿到了该公寓楼的锁,那么他就可以进入该公寓楼并且可以随意进出各个房间,在这期间如果另一个人访问该公寓楼,因为锁被其他人所持有,所以后来的人只能在大门前等待,直到持有锁的人退出公寓并告知其他人锁已经被归还。由此理解synchronized块也比较容易,可以把synchronized块理解成房间里的被上锁的物品,其他人如果想使用该物品则必须持有锁。对于没有上锁的物品则可以随意访问。

在这次作业中遇到的最大的困难便是线程交互。线程交互可以通过线程同步来实现,因此难点就在于如何同步三部电梯与调度器。我们需要实现在一个扫描周期内,电梯状态不发生改变,在分配任务的过程中使得三部电梯的运动量尽量均衡。

要想保证负载均衡原则,那么调度器就需要了解各个电梯的基本信息,我之前的做法是直接在调度器中访问三部电梯的对象,由电梯对象获知各种信息。不过,这种做法显然使得类之间的耦合较大,老师提供了另一种方法,新建一个“发布板”,电梯向“发布板”中上传自己的状态,而调度器则从“发布板”中获取相应的信息。第二种方法明确了各类的功能职责,同时,很容易构造线程安全的类。

在这次作业中还有其他的问题需要解决,对于每部电梯线程,是直接访问主队列,还是新建各自的请求队列?“发布板”更新的细节等等。总之,这次的多线程电梯调度的难度提高的不是一两个台阶,做的时候很痛苦。。。

作业BUG

初次尝试多电梯,bug还是挺多的。首先便是同质请求,我是通过“点灯”系统来实现同质的判断的,即对于某一个请求,在该请求时刻如果对应的”灯“还未熄灭,则该请求是同质请求。不过,在具体实现的过程中出现了问题,导致同步请求没有被判断出来。其次还有请求的分配,并没有完全做到负载均衡的策略。

程序的圈复杂度和嵌套的块深度比较大,且分析类的代码行数也可以发现,这次的设计功能分配不均衡,导致调度器代码过于冗长,而有些类的功能相对简单。


二、IFTTT

 如果把前几次作业视为情节递进的一部催人“泪下”的番,这次作业更像是“番外篇”。。。这次作业的目的也很清晰:进一步掌握线程的概念以及加强理解线程安全的设计,作业难度与多线程电梯不相上下。作业的要求是实现基于作业监控的触发任务,我基于如下的思路。

监控目录可以看做是监控文件的集合,监控件是监控目录的特例,因此,两者有一些共性的东西,但又有各自的特点。例如,对于监控文件,我把监控范围设为监控文件的当前目录,则其搜索行为与监控目录无二。通过快照,对比前后两次监控范围内文件的变化,从而触发相应的任务。这次作业的思路极其简单,但是具体实现细节则是非常之多。。。我们可以举一个具体的例子。

在某个路径下存在除文件名外其余信息完全相同的三个文件A、B、C(这可以通过复制粘贴来实现)。我们实现对文件A的监控,当文件A被重名后触发恢复操作。好了,现在我们进行如下操作,把文件A删除,那么理论上监控对象应该丢失,触发器不被响应。可是,我估计有些人的程序会把文件B或者C作为文件A重名后的文件,从而对B或C进行恢复操作(我的程序会这样。。。)。问题出在快照对比这一块,仅仅进行了单向检查,只进行了从前向后的对比,并没有进行从后向前的对比。当文件A被删除后,我们发现最新的快照里文件A不见了,这时我们搜索到了文件信息除了名字不同的文件B和C,那么,我们并不能立刻得出文件B或C是文件A重命名之后的文件,只有当文件B或C在原来的快照里不存在,我们才能得出这个结论。

此外,当监控对象为文件时,还需要额外注意一个细节。我们来看另一个例子。

在某个路径下只存在文件A,我们实现对文件A的监控,当检测到文件A被修改记录详细信息,文件A大小发生改变记录详细信息,文件A被重命名记录详细信息。我们进行如下操作,首先,改变文件A的最后修改时间,然后向文件A中写入内容,最后重命名文件A为文件B。理论上所有的触发器均应被触发,但实际运行的结果更多的可能是只监测到了文件被修改和文件大小被改变,文件重命名没有被触发。其中的一个可能原因是:当我们改变了文件A最后的修改时间后,程序所存储的监控文件还是原来的最后修改时间,这里出现了时间上的不一致,导致的后果是,我们在比对文件A和文件B时因为时间上的不同而使得程序认为文件A没有被重命名。所以,在对文件修改后,包括最后修改时间和文件大小的改变,我们需要实时更新所监控的文件的属性。

作业BUG

这次作业的bug基本就在我上面所讨论的情况中了,这次作业细节的实现真的超级多啊,一个不小心就会跳出某个bug。。。

 这次作业主要体现在圈复杂度上,InputHander类的功能是对输入进行处理从而保证程序的鲁棒性,因此,圈复杂度较高是正常的。观察类行数可以发现,这次的作业代码相比上次有一些进步,但是在监控器里功能相对复杂,将四种触发器融合到了一起。


 三、出租车调度

 终于迎来了一次较为轻松的作业,虽然这次作业的线程数量之多有些吓人,我是将出租车作为线程的,固定为101个线程,我猜应该没有人把请求作为线程的,请求数量比较少的时候还可以,但是一旦1000个请求就要启动1000个线程,想想就很刺激。。。

这次作业类似于多线程电梯调度,但是实现起来要比多线程电梯简单多了,主要是少了捎带请求的判断,(下次出租车调度不是要考虑拼车吧。。。)因此实现起来还是比较简单的。不过这次作业的要求比较严格,要实现”SOLID“原则,层次化原则,责任均衡原则等等一大堆原则。。。这次作业 找出来 的bug是输入检查,没有考虑制表符的存在。。。我这里用到是 找出来 的bug,也就是我并不知道程序是不是存在功能性的bug。。。总之,这次作业很愉快的就过去了。

 同样的,这次作业的圈复杂度还是体现在输入处理的类中,这次Taxi类的功能比较复杂,几乎是重写之前的调度器。。。


小结

在多线程电梯调度和出租车调度中,都有对相邻时间差的要求,在涉及到时间的要求上,我基本都是通过“模拟时间”来解决的。在检查对方的bug时,采取的阅读代码+打印输出的方式。。。

最后希望之后的OO之旅能轻松些。

猜你喜欢

转载自www.cnblogs.com/buaawang/p/8976918.html