第二单元多线程电梯总结

第二单元面向对象作业以多线程为主,通过开始的傻瓜电梯,功能逐渐增多,数量逐渐增多,三次作业的练习强化了我们对于多线程的理解,并且提高了我们的架构设计能力。

一.设计策略

  第五次作业傻瓜电梯

  每次只需要取出一条最先输入的指令进行执行,无需考虑时间和顺搭,只要将人送到目的地即可。架构设计类似于生产者和消费者,一共创建了三个线程,包括一个主线程,电梯对应一个线程,输入对应一个线程,该线程还有主调度器,每次读入一个命令后,通过调度器直接插入电梯等待执行命令的队列中,这个队列就相当于生产消费中的仓库,生产者就是输入线程,消费者就是电梯本身,队列保证线程安全,向队列插入和从队列取出命令执行上同一把锁并配合wait和notify使用即可,架构较为简单。

    第六次捎带、第七次多电梯

  后两次引入了捎带和多电梯后,架构设计会变得复杂,主要是因为电梯并不按照命令的输入顺序进行执行,除了考虑架构,还需要考虑如何正确的将程序结束,不出现遗漏指令未执行或者电梯无法正常结束的情况。

  第六次捎带电梯

  和第五次类似,线程数量和作用相同,捎带也需要调度,我的主调度器只负责输入指令就将指令送到电梯,捎带的子调度器在电梯类中设计。架构也是生产者消费者,插入取出配合指令上同一把锁并配合wait和notify,不同的是电梯类中有两个存储命令的容器,一个是存电梯将要执行的命令,另一个就是存储暂时不执行的命令,取命令从将要执行的命令队列中取,插入命令时根据情况选择插入哪个容器中即可。

  第七次捎带电梯

  和前两次不同的地方就是多了三个线程,多了两个电梯就多了两个进程,还有一个线程专门负责处理中转指令,只有当前一条指令执行完毕后才能将后一条指令插入对应电梯中,电梯自身调度器还是以捎带为主,主调度器增加了功能,判断指令是否需要进行拆分,需要则将其拆分,并且将指令送到对应的电梯中。电梯内部取指令和插入还是生产者消费者架构。

二、程序结构分析

  第五次较为简单,不做详细分析。

     第六次作业

  主要分析电梯的优化设计,电梯运行一次分为两步,第一步到达起始楼层,第二步到达终止楼层;电梯运行只有向上和向下两个方向,如果当前电梯运行方向和命令方向相同,如果向上,当前楼层小于命令的from就捎带,不能捎带就放入存储不执行命令的队列中。当电梯一次运行后(以向上为例)结束后,那么就从存储暂时不执行命令容器中取出所有向下的命令放入待执行容器,然后再次运行电梯,之所以取向下的,因为这种捎带策略电梯向上运行时已经保证到达了向上命令中的目的地最高的楼层。电梯结束的标志为读入NULL,而且电梯中两个存指令的容器长度均为0。

  缺点

   1.没有完全将调度器和电梯分离,即没有做到oo要求的每一个类只做自己的事情,代码耦合度不高;

  2.捎带代码的编写过于面向过程,需要判断的情况过多,无法做到checkstyle中方法不超过60行的要求,只能够将部分代码单独拿出来再构造一个方法。

  3.捎带的设计不够优秀,BUG过多,需要大量测试保证正确性。

  优点

  电梯的运行性能较为良好,强测结果可以体现,而且第七次作业再加的话较为简单,由于主调度器不负责捎带调度,所以主调度器只需要加入指令拆分的功能,完全不需要重新架构。

      度量分析和UML类图

  

   

  第七次作业

  先分析优化,还是以捎带为主,分配任务时,指令能不拆分就不拆分,先往AB放,因为运行时间短,为了让C也能分到命令,插入指令时还要判断是否超载(电梯人数加待执行命令总数作为依据),超载而且C能跑就往C扔,这样保证三台电梯尽可能的达到同时运行效果。

   主要分析换乘电梯的设计,必须保证指令拆分后前一条指令执行完(即人达到目的地)才能执行第二条指令。我又开了一个线程,这个线程中有一个容器,存着所有拆分完后的第二部分指令,每当有一个人下电梯后,先判断这个人是否需要换乘(我添加了一个标志位),如果需要换乘就唤醒负责中转指令的线程,遍历该容器,根据人员id相等找出第二部分指令,插入对应电梯即可,该线程执行完一次就wait,即只有当前一部分完成后才会切换到中转线程。每部电梯结束标志为读入NULL而且指令容器长度为0,而且中转线程的容器中没有该电梯需要执行的指令。

  缺点

  还是没有做到一个类只负责做一件事的设计要求,而且每次换乘第一条指令执行完后唤醒中转线程,无法做到精确打击,而是需要通过遍历找出对应的第二部分指令,指令数量少不影响,指令多的话会影响效率。

  优点

  处理换乘单独创建的线程没有使用轮询,用wait和notify代替,减少CPUTIME,而且只用一个线程就可以解决换乘,而不是出现一个换乘就开一个线程处理。

       度量分析和UML类图

 

三、BUG分析

  1.当楼层出现负层时,会出现0层开门的情况

  优化算法选最低楼层时我默认最底层为0层而不是-3层,会出现问题,而我自动生成的数据全是0层以上的楼层,没检查出来。

  2.电梯无法正常结束和运行

    1.第一次是因为当我插入NULL的时候没有唤醒线程,第二次是因为输入NULL唤醒线程没有退出死循环;

    2.第七次作业还出现了只有一条捎带指令不执行电梯直接结束的情况,通过大量的System,类似于设置断点,发现是因为插入拆分指令都是在另一个线程中完成,由于线程未切换,输入线程直接又插入NULL电梯就结束了。

  3.容器的选择问题

  由于电梯执行指令最好是先进先出,符合队列的思想,所以我一开始使用queue容器,由于只有头尾两个指针,遍历的时候十分麻烦,只能先取后放,用这个容器做第七次作业就有问题,单独有一个中转进程遍历待执行的第二部分指令,用队列可能出现从队列取出来指令,发现不是,还没有扔进队列就切换执行其他线程了,如果此时读入NULL,也会出现电梯提前结束的情况,后来我就改为使用容易遍历的容器Vector。

四、互测分析他人BUG

  我没有写出一个程序去判断输出结果是否满足输入的合理性,互测时主要学习他人代码,分析他人代码的大体架构,而没有以检测正确性为主要目的,提交样例时只提交一些易错点。

    第一单元要考虑很多的细节,主要是有WRONGFORMAT的需求,每一个细节都可能成为成功hack别人的点。但是本单元电梯只考虑运行正确性和性能,要求较为简单,只考虑人是否到达目的地即可,所以出现错误的情况很少,只要构造40条强测指令没有错误,基本就不会再出现问题了。

五、心得体会

  线程安全方面

  任何多线程的框架设计都必须保证安全性,多个线程访问同一个变量时就一定要保证不能同时改变值。

    本人线程安全的设计

    1.不同进程访问同一个int型变量,可以将其设置成原子化的性质

    2.用进程安全的容器Vector链表代替不安全的Arraylist

    3.不同进程方法需要同步时在方法面前加锁,或者可以自行设计一个对象当锁去锁住部分代码块(代码块用一个方法单独拿出来,在方法前上锁效果一样),而且电梯作业一定不能将含SLEEP方法的内容上锁。

  设计原则方面

  1.同一个问题会有多种设计模式,而且很可能出现不同设计模式之间效果虽然一样,但是差距巨大的情况。但基本都是在已有的模式架构中进行改变。

    2.设计时还需要注意线程之间可能存在执行时相对顺序的问题,如果无法满足多线程执行的先后顺序,由于线程真正运行时的快速切换,会出现无法预见的错误情况,而这种设计的错误很难发现。

 

猜你喜欢

转载自www.cnblogs.com/17373395c/p/10747464.html