BUAA_OO_Unit2 多线程电梯调度

王增煦-22373137 2024-04-20 16:27:22

BUAA_OO_Unit2 多线程电梯调度

一、前言

在本单元中第一次接触到多线程程序,因此在开始作业之前花了很长一段时间来学习多线程、互斥锁等内容。

  • 在单线程程序执行时,同一时刻只有一段代码在执行,且属性仅由该线程使用,而多线程则是同时多个线程执行,在java中可以通过继承Thread类或者实现Runnable类接口来实现多线程,多线程运行过程中可能会同时访问相同对象,这时不得不引出线程互斥问题:如果某一线程正在对共享对象执行某种操作时,那么其他所有线程都不能进行对该共享对象执行操作。

  • 生产者-消费者模式:生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

    • 实现方法:使用 synchronizedwait、notify

      public synchronized void putRequest(PersonRequest request) {
              requests.add(request);
              notifyAll(); 
          }
      
      public synchronized PersonRequest takeRequest() {
          if (!isEnd && isEmpty()) {
              try {
                  wait(); // 等待队列为空时调度线程等待
              } catch (InterruptedException e) {
                  throw new RuntimeException(e);
              }
          }
          ...
          notifyAll();
          return request;
      
      }
      

二、同步块的设置和锁的选择

当涉及到共享对象读写的代码时需要进行加锁,而同步块的选取不能过大也不能过小,同步块选取过大时会造成性能的降低,选取过小可能会造成同步块逻辑不完整,造成错误。

  • 锁的设计:

    • synchronized关键字:

      synchronized(obj) {...}
      synchronized method {...}
      
    • ReadWriteLock(将读写分开):

      private final ReadWriteLock lock = new ReentrantReadWriteLock();
      
      public int readData() {
          lock.readLock.lock(); // 获取读锁
          try {
              return data; // 读取共享资源
          } finally {
              lock.readLock.unlock(); // 释放读锁
          }
      }
      
      public void writeData(int newValue) {
          lock.writeLock.lock(); // 获取写锁
          try {
              data = newValue; // 写入共享资源
          } finally {
              lock.writeLock.unlock(); // 释放写锁
          }
      }
      

三、作业架构设计

UML类图:

img

  • RequsetQueue:实例化一个WaitList整体请求队列,以及每个电梯内部的processingQueue等待队列和passengers乘客队列。
  • InputThread:输入数据到WaitQueue,结束时设置其isEnd
  • Schedule:分发请求,将WaitQueue中的请求根据调度策略分配到电梯内的processingQueue
  • Elevator(单轿厢电梯类线程):实现单轿厢电梯线程运行逻辑。
  • DCElevator(双轿厢电梯类线程):实现双轿厢电梯线程运行逻辑。
  • Occupied:双轿厢电梯的共享对象,设置临界快,防止两部电梯同时进入调度层。

UML协作图:

img

调度策略:

  • 所有电梯均可见总请求队列(WaitList),每部电梯内置自己的等待队列(processingQueue),通过调度器(第二次作业加入)将请求队列中的请求加到调度电梯的等待队列当中。

  • 电梯内有乘客时执行捎带策略,即LOOK策略

    • 电梯捎带是LOOK算法,即遵循电梯内请求为主请求,捎带过路者的策略。当路过一层楼时,如果电梯内有乘客到站,就开门让其下电梯;如果该层有同电梯运动方向相同的请求且电梯内人没有满,就开门让其进入;如果不符合以上两点,并且电梯内有人或者没人但同方向有请求,就按原方向移动;如果不符合以上三点,并且电梯内没人,则电梯掉头,并重新判断同方向是否有新请求。除此之外的情况,电梯进入等待状态。

    • 当电梯经过某一楼层时,如果有乘客在电梯内要到达该楼层,则开门让他们下电梯。

    • 如果该楼层有与电梯当前运动方向相同的请求,并且电梯内还有空间,则开门让这些乘客进入。

    • 如果电梯内有乘客,或者没有乘客但是同方向有新的请求,则按照原先的运动方向移动。

    • 如果电梯内没有乘客,并且当前方向上也没有新的请求,则电梯会改变方向,并重新检查是否有新的请求。

    • 除了以上情况,电梯将进入等待状态。

第二次作业新增:

1. 不再指定电梯,需设计调度器分配电梯完成乘客请求

  • 模 6 调度:即在电梯的调度上,可以采用一种较为均衡的调度方式,例如7层有5个乘客,3部电梯,那么第1个乘客分配给第1部电梯,第2个乘客分配给第2部电梯,第3个乘客分配给第3部电梯,第4个乘客分配给第1部电梯,第5个乘客分配给第2部电梯。
  • 影子电梯:通过深克隆六部电梯,模拟运行时间,算出局部最优解的电梯,将请求加入其等待队列。

2. 电梯重置RESET

对于电梯重置则是在电梯接收到重置指令后尽快停靠,放下乘客完成重置动作后再投入电梯系统运行(修改其性能参数(满载人数、移动时间))。起初我把它理解为,当接收到RESET指令,结束该电梯线程,并重新实例化一个具有RESET参数的新电梯线程,但是后面发现当调度线程结束后,实例化新电梯的操作不易实现。于是就没有将重置电梯线程结束,而是让其sleep(Treset)并修改参数,继而投入使用。

  • 电梯内设置isReset标记电梯是否正在重置过程中,使调度器跳过该电梯。
  • 重置电梯需要将乘客放下,并修改其getFromFloor为当前楼层,并将等待队列重新加入到总请求队列中。
  • 修改调度线程结束条件:在InputThread线程中输入为空时,且要求没有电梯正在重置时,设置isEnd

3. RECEIVE约束

  • 当请求分配到某一电梯等待队列中时,输出对应RESEIVE,需注意电梯重置完成后再将其中请求进行分配并RECEIVE

第三次作业新增:

双轿厢电梯DCElevator

  • 新增的双轿厢电梯运行策略与普通电梯基本相同,但是关键点在于换成楼层的处理。这里定义了新的类,双轿厢通过争夺Occupied对象的锁,保证不同时进入换乘楼层。
public class Occupied {
    private int state;

    public Occupied() {
        this.state = 0;
    }

    public synchronized void setOccupied() {
        waitRelease();
        state = 1;
        notifyAll();
    }

    public synchronized void setRelease() {
        state = 0;
        notifyAll();
    }

    public synchronized void waitRelease() {
        notifyAll();
        while (state == 1) {
            try {
                wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

}
  • 在双轿厢move中进行判断是否能进入换乘楼层,不行则等待。

    public void move(int dir) {
          int floor0 = floor;
          floor = (dir > 0) ? floor + 1 : floor - 1;
          try {
              sleep(speed);
          } catch (InterruptedException e) {
              throw new RuntimeException(e);
          }
          if (floor == transferFloor) {
              occupied.setOccupied();
          }
          TimableOutput.println("ARRIVE-" + floor + "-" + id + "-" + type);
          if (floor0 == transferFloor) {
              occupied.setRelease();
          }
    
      }
    

三次作业稳定的内容和易变的内容

  • 不断增加了线程的控制以及线程控制的条件,以及对共享变量访问安全性的要求。

  • 电梯运行逻辑基本不发生改变。

    public void run() {
          while (true) {
              if (isReset) { // 是否重置
                  reset();
              }
              if (openDoor()) {// 是否开门
                  openAndClose(); // 进出乘客
              }
              if (count != 0) {
                  carryStrategy(); // 执行捎带策略
              } else {
                  strategy();
                  if (isReset != 0) {
                      continue;
                  }
                  synchronized (processingQueue) {
                      if (processingQueue.isEmpty()) {
                          if (processingQueue.isEnd()) {
                              break;
                          }
                          dir = 0;
                          try {
                              processingQueue.wait();
                          } catch (InterruptedException e) {
                              throw new RuntimeException(e);
                          }
    
                      }
                  }
              }
    
          }
    
      }
    

四、多线程bug

多线程bug主要集中在 TLE 问题上。

  • 轮询造成 CTLE :可以在循环中插入输出函数锁定轮询的位置。使用wait而不是continue轮询。
  • 多层synchronized嵌套顺序不一致造成死锁。
  • 线程方法不要无脑加synchronized,对于读取或修改共享对象的方法需要加,否则可能造成死锁。

五、心得体会

  • 对于共享对象的读写语句块或方法需要加锁。
  • 注意多个线程访问同一共享对象时,锁的对象是同一个
  • 多层synchronized要保证嵌套次序一致。
  • notifyAll的用法是唤醒wait中的线程,避免在synchronized语句块中滥用notifyAll方法,导致唤醒没有wait的线程造成CTLE。
...全文
55 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

301

社区成员

发帖
与我相关
我的任务
社区描述
2023年北航面向对象设计与构造
学习 高校
社区管理员
  • YannaZhang
  • CajZella
  • C_ecelia
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧