OO第二单元总结

OO第二单元总结

作业综述

本单元的训练主要目的是目标是模拟多线程实时电梯系统,熟悉线程的创建、运行等基本操作。作业的核心内容为实现一个多线程的目的选择电梯调度控制系统,并要求按固定格式输出电梯运行过程中电梯到达楼层和开关门的信息以及乘客进出电梯的信息。其中,第一次仅有 纵向电梯,第二次增加了横向电梯,但是乘客只会请求乘坐纵向电梯和横向电梯中的一种,第三次作业在第二次作业的基础上,允许自定义电梯而且允许顾客提出换乘请求。

同步块和锁

多线程

如果所有程序都操作的是不同的对象,彼此之间没有干扰,那么多线程和单线程差不多。

而java允许多线程并发控制,当多个线程同时操作一个可共享资源变量时(如对其进行增删改查操作),会导致数据不准确,而且相互之间产生冲突。所以需要通过线程之间的协同来保证该变量的唯一性和准确性。

同步锁实现协同

java中常用的实现线程协同的方法是synchronized关键字。它可以给对象和类上锁,使得多个线程遇到这段语句块时,只有一个线程能执行。这样就保证了实际上的执行是串行执行的,不会有执行中途被其他线程打断的问题。

除了synchronized,Java还提供了阻塞队列来实现线程同步、使用原子变量实现线程同步原子操作、使用局部变量实现线程同步等,如在此生产者——消费者者模式中则可以使用BlockingQueue来实现线程的同步。它提供了两个支持阻塞的方法:put(E e):尝试把元素e放入队列中,如果队列已满,则阻塞当前线程,直到队列有空位。take():尝试从队列中取元素,如果没有元素,则阻塞当前线程,直到取到元素。

在加synchronized关键字修饰的方法中,因为每个java对象都有一个内置锁,当用synchronize关键字修饰方法时内置锁会保护整个方法,而在调用该方法之前,要先获得内置锁,否则就会处于阻塞状态。同样,拥有synchronize关键字修饰的语句块,被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。

而在本单元的在第一次作业和第二次作业中,共享对象均是只有一个,就是实现的RequestQueue类。这个类是储存Request的容器对象。在确定了共享对象后,在实现过程中基本上所有的同步锁synchronized只需要加在这个类中,除了为了避免轮询使用wait()的时候;而在第三次作业中,我也仍然是对于共享对象进行加锁,这次的共享对象有两个MainQueuePersonQueueMainQueue中还有一个PersonQueue,这样实现的目的主要是由于第三次作业中需要将需求进行分段之后,每一个电梯完成某一阶段的任务之后需要将下一阶段的PersonRequest重新加入到调度器可以调度的对象中,因此将MainQueue做成单例共享对象。

调度器

在三次作业中我的调度器均差不多,由于采用的是自由竞争策略,只需要在最初的生产者消费者的结构中使用调度器将生产者生产的需求分配至对应的楼层。

此调度器由于需要实现根据需求的要求将等待队列中的某一个请求分配到能够处理此请求的楼层中,因此在此调度器中需要得到一个能够访问到每一个楼层和楼座的容器,因此我对应地设计了两个单例的容器对象,这两个容器中的主要属性就是一个HashMap用来访问到每一个楼层来加入请求。

因此调度器与其他楼层的交互都是通过这两个Map进行的。

调度器最终需要实现的功能也并不复杂,最主要的就是void scheduleRequest(Person person),分配过程中是它作为一个消费者将第一层队列中的Person根据它的nowRequest来分配至对应楼层中的队列中。

作业架构分析

由于三次作业的架构大体上一样,第二次作业是在在第一次作业的基础上为了更好地存储和访问楼层信息,新设计了FloorMap 与BuildingMap,对于第三次作业比第二次作业将PersonRequest封装为Person以便于通过标记nowRequest来处理分段的请求。这里主要画出第三次作业的图。

后两次两次作业架构中,为了实现横向电梯与纵向电梯的状态访问以及增删电梯,添加了了两个自定义的容器,然后在第三次作业中为了在输入线程得到一个请求时就能第一时间进行分段,添加了每一个横向电梯可达信息的容器SwitchInfoMap

bug分析

  1. notify()
    在我的实现里,同一栋楼的所有纵向电梯或同一层楼的所有横向电梯公用一个共享对象。我在isEmpty()isEnd()方法中写上了notifyAll()。问题在于,当一部电梯没请求在wait()状态时,如果另一部电梯调用了isEmpty()方法,就会将所有处于wait状态的电梯都唤醒,但是由于并没有请求分配给这些电梯,就会导致cpu空转,造成超时。修复bug的方法只需减少不必要的notifyAll()即可。
  2. 结束标志
    第一次作业中在结束电梯线程时,程序出现错误,导致最终电梯未能结束,运行时间超时。分析之后发现这次作业中判断是否结束电梯线程时,在进入while循环后写了一个wait(),可能是强行模仿了训练的代码,导致wait()唤醒后,又进入了一次wait()。所以没有线程来唤醒本次的wait(),最终线程无法正常结束。

心得体会

本单元的架构我的思路是让输出入模块调度器模块和电梯模块尽可能分工明确,为了满足单一责任原则,设计时将生产者线程只负责读入请求,并将其加入请求队列或添加电梯;调取器仅负责将请求队列中的请求分配给电梯各自的队列;消费者线程负责处理请求。三次作业均使用第一次作业设计的电梯,基本无需改动。

本单元的作业是我第一次接触多线程编程。通过这次作业,我进一步加深了对多线程的理解,并初步掌握了多线程编程的要点,理解了线程同步、线程安全等问题,也增加了多线程调试的经验。

posted @ 2022-05-04 10:40  百觅  阅读(28)  评论(0编辑  收藏  举报