BUAA面向对象设计与构造第二次博客总结

BUAA面向对象设计与构造第二次博客总结

1. 同步块和锁

第一次作业

第一次作业由于我本身对于多线程的同步块和锁还不是很熟悉,所以为了保险直接把调度器的所有的方法本身用synchronzied修饰,也就是说只有一个同步块,那就是调度器这个类,而电梯线程或者输入线程每时每刻只能有一个进入该类,且拿到锁,然后执行自己的任务,而其余的线程只能等待拿到锁的线程执行完毕后将锁释放,从而再竞争锁,拿到所得线程进入同步块中执行,其余线程再次等待……很显然,这次作业的设计是效率很低的,只是由于最多就一个电梯,所以采用这种方法的效率低的缺点没有怎么显现出来。

synchronized void func1(……) {
   
}

synchronized void func2(……) {
   
}

 

第二次作业

第二次作业我改变了做法,由于最多可能有五部电梯,所以我在调度器中一开始就构造了五个请求队列,分别对应1,2,3及可能有的4,5号电梯。然后分别讲每个请求队列本身用synchronized修饰,及如下的方案:

public void func(……) {
   if (……) {
       synchronized (list1) {
           //do something
      }
  } else if (……) {
       synchronized (list2) {
           //do something
      }
  } else if (……) {
       synchronized (list3) {
           //do something
      }
  } else if (……) {
       synchronized (list4) {
           //do something
      }
  } else {
       synchronized (list5) {
           //do something
      }
  }
}

上面是我的scheduler类中的其中一类函数模板,可以看到,每一个list是一块同步块,用synchronized加锁确保每时每刻只能有一个线程访问某一个list。上述方法的效率比第一次作业要高,原因是虽然每一个list还是只能“进”一个线程,但是list和list之间并不互相影响,也就是说elevator1访问list1的同时elevator2访问list2是完全没有问题的,而如果还是采用方法一,那么每时每刻,这五个list最多只能有一个被一个线程访问,效率大幅降低。

第三次作业

第三次作业我在对于锁和同步块的策略上是和第二次作业完全相同,故同第二次。

2. 调度器和线程

第一次作业

第一次作业的调度器我设置了三个私有成员,一个是用来存放输入线程输入的请求,并由电梯线程所拿走的lists,其类型是Vector;一个是表示输入线程是否结束的标志,配合电梯线程的结束;一个是Integer类型的total,表示目前还在lists的成员个数。并定义了若干个函数,包含但不限于增添请求的addRequest,获得第一个请求的getFirstRequest(),检查当前层是否有被捎带对象的checkByWay(int, int),让满足条件的 人上电梯的getOn(int)等。调度器与输入线程和电梯线程交互的方式是电梯接受输入线程的输入请求,并将其放在lists中,并唤醒正在等待的电梯线程。电梯不断自己朝着目标楼层运行,并没到一层,会调用checkByWay()和getOn()挑选符合条件的人并让他上电梯。最后,当输入线程结束的时候,调度器将endFlag置位true,从而也告诉电梯线程输入线程已结束,当电梯线程再一次为空,检查调度器地lists为空且endFlag为true时,会自动结束,进而整个程序结束。

第二次作业

第二次作业和第一次原理上基本上一致,所不同的是为不同的电梯设置了不同的队列:listsA,listsB, listsC, listsD, listsE,并且为每个队列都设置了一个代表当前队列中请求个数的成员:totalA,totalB,totalC,totalD,totalE。并且增加了增加电梯的函数addElevator(String, String),当输入线程接受增加电梯的请求的时候,会调用这个函数从而创建另外以步电梯。请求交互的过程与第一次基本一致,对于每一个电梯而言,通过调度器和输入线程写作的方式与第一次完全一样。最后,当每个电梯线程都结束的时候,整个程序结束运行。

第三次作业

第三次作业我基本上就是偷懒水了过去,没有做换乘这一设计,而且对于电梯的创建问题为了省事也偷了个懒,只创建A类电梯,而如果请求创建其他电梯,则忽略什么都不做。经过了这两个方面的"化简",第三次作业的调度器和协作策略基本上和第二次完全一样。唯一增加的就是简单的请求分配策略,即:如果该请求可以放到C类电梯,就放到C类电梯,若不能如果能放到B类电梯,那么就放到B,如果还不能,就放到A。

3. 程序分析

第一次作业

功能设计:满足了题目的所有要求。

性能设计:由于强测有bug,故不清楚性能究竟如何。不过我认为这次强测的性能应该还是不错,采用的调度算法比较高效。

可扩展习性:可扩展性方面还是比较好的,而且事实证明了通过在这次架构的设计上,只需做出很小的改动便能够实现第二次作业和第三次作业。

UML类图:

UML顺序图

第二次作业

功能设计:满足了题目的所有要求。

性能设计:性能分比较令人满意,得到了93+的水准。我的算法在Morning和Night两种模式下表现得性能十分高效,而普通的Random模式表现得不尽如人意。

可扩展性:可扩展性还是可以的,但是第二次不利于加入不同类型的电梯,这一点在第三次作业上表现出来。

UML类图

UML顺序图

第三次作业

功能设计:没有满足全部的功能要求,写的时候偷了点懒,只识别增加A类电梯的指令,而B类电梯和C类电梯自始至终都只有一台在运转。

性能设计:虽然没有完全实现全部的功能,且调度算法在此处也没有实现换乘,但是性能分仍然表现得不错,得到了92+的水准。

可扩展性:本次可扩展性并不理想,由于并没有完全根据题目要求做出更好的程序框架,故对于不同类型电梯的扩展性并不理想。

UML类图

UML顺序图

4. 自身bug分析

第一次作业

第一次强测状况异常惨烈,出错次数占一半以上,但是错误的原因却十分的智障,就是因为我的电梯在判断人是否已满的函数出了错误,导致电梯满载的人数比题目设置的多一个人,而强测凡是按我的算法会出现超载的全部出错。修改bug异常简单,就是加一个等号即可。

顺便补充一个课下自测发现的bug,当我一次性输入很多的请求,并紧跟着输入NULL结束输入线程时,我发现我的程序会提前结束,也就是说在某一点电梯线程会自动退出,而忽略了还在调度器中未被满足的请求。后来根据我的检查发现,我在判断电梯线程是否需要推出的时候,只是判断了调度器的endFlag是否为true和此时电梯的队列是否为空,而没有判断调度器队列是否为空。所以当电梯送完第一波人之后,本身队列为空且endFlag为true,那么自然就退出了。后来把判断条件加上一个判断调度器的队列是否为空就不会出现问题了。

第二次作业

强测没有发现bug。

第三次作业

强测没有发现bug。

5. 发现bug策略

本单元我并没有怎么参加互测,也并没有查到同房间成员的bug,所以以下我就按照我对查bug的理解来说明一下。

首先,如果并不想通过阅读对方的代码来查找bug的话,最好的方法就是设计几组大部分请求在同一时间到来的比较好。这样的设计是考虑到由于本次作业所涉及的是多线程,所以在同一时间段内到来大量的请求可以比较好的检验程序多线程处理的正确性,而且,还可以顺便检验出是否出现允许超载等低级bug。另外,在针对第二次和第三次作业的检查中,可以将增加电梯的请求和许多条请求一同到允许输入时间的最后一刻在输入进去,并将请求电梯的指令放到最后或者靠后的位置,这样做的原因就是可以有效的检验该程序的调度策略是否比较高效,如果有人采取傻瓜电梯或者其他比较低效的调度策略,那么就有可能超时。之所以将电梯请求策略放到最后,是因为按照有些人的算法(我的就一样),如果电梯最后添加,的确会启动该电梯线程,但是该电梯线程并不会得到在电梯请求之前输入的人员请求策略,所以这样做也是能够检验被检验程序调度算法的合理性,如果不合理,那么超时的可能性会比较大。即使在这一单元中,由于要求等原因或许不太容易测出超时,但是这个方法也是值得一试的。

而如果肯仔细读对方的代码的话,那么我认为首先还是先检查电梯策略有没有错误的情况。首先这种情况还是比较常见,尤其是如果将电梯的运行状态、策略等设计的非常复杂的那种,如果没有仔细审查电梯各种状态之间的转换,论证电梯策略无误,那么在某些比较特殊的情况下出现错误的可能性还是不小的。如果经过自己的检查,发现电梯的策略并没有错误,那么就可以检查以下该代码的线程协作是否没有错,比如检查程序是否可能会出现死锁、程序是否可能会提前结束等。当然,我并不认为通过中测的人会出现非常明显的线程错误,就算是有出错也应该是比较隐晦的错误,检查的人也不容易发现。

6. 心得体会

        第二单元的主要内容就是对于多线程的学习,并在理解掌握了多线程的基础知识之后编出一个电梯的程序。不得不说,刚开始我对于多线程的理解是有很多问题的。我在第一单元的博客周就开始预习多线程,读了相关的书籍,并且掌握了最基本的知识点,例如创建线程的两种方法,锁的作用等。当时我对于多线程的理解还远远不够,只能照着书本打一些简单的程序。但是,不得不说这段预习还是十分重要的,毕竟它帮我扫盲了许多,让我在课上听讲的时候对于老师讲的基础知识有所了解,并将精力更加专注于一些细节和思想的理解上。

        而三次作业完成之后,我再回顾我的这一单元的经历,我的总体感觉还是比较满意的。最起码我还是顺利完成了三次作业,并且后两次在强测中拿到了我比较满意的分数。但是,我仍然有许多并不是很满意的地方,其中之一就是我发现这一单元自己表现得有些懒惰和胆小了,直接的体现在于我第一次的架构做的还算不错,足够将后两次作业应付过去,然后因为这个我就在后两次作业放弃了能取得更好效果的重构,而是就在第一次作业的基础上修修改改。最后虽然过了三次作业,但是我本来还可以做的更好。当然,这一部分原因是我的确懒得重构,但是还有一部分原因其实是因为我对于多线程还是有一些畏惧的。如果重构不好,可能会出现让我百思不得其解的bug,为了避免这样的情况出现,我最终选择了在第一次经过测试大概率没问题的架构上进行修改,虽然可能代码会丑陋一些,但是最起码是能够保住底线的。当然,我也希望我自己可以在暑假没事的时候,再尝试编出更好的电梯出来。还有一点,我觉得我还是比较幸运的,因为我在这三次作业中并没有出现不可复现性的错误,也就是说我的线程设计一开始是没有什么大的问题的。如果我出现了那种不可复现的错误,那么我的debug时间毫无疑问将大幅提高。

        总之,这个单元顺利的过去了。马上就要进入新的单元的学习,希望我自己在这个单元的学习中,可以不怕出错,不怕麻烦,更多的动手尝试,获得更多的知识和方法。

posted @ 2021-04-26 21:18  buaa_lpx  阅读(53)  评论(0)    收藏  举报