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

第一次作业

同步块

RequestList是输入进程Input和调度器进程Manager之间的临界区,主要用于存储输入的请求。

EleState是调度器进程Manager和电梯进程Elevator之间的临界区,主要用于存储电梯的状态。

Input中的锁

向RequestList中插入请求时,锁RequestList

while (true) {
   request = elevatorInput.nextPersonRequest();
   synchronized (requestList) {
       if (request == null) {
           requestList.setNoMoreInput();
           requestList.notifyAll();
           break;
      } else {
           requestList.offer(request);
           requestList.notifyAll();
      }
  }
}

Manager中的锁

从RequestList中读取请求时,锁RequestList

调度算法运行时,需要查询电梯状态,锁EleState

synchronized (requestList) {    //读取输入请求
   //退出程序
   if (requestList.isNoMoreInput() && requestList.isEmpty()
       && requests.isEmpty() && eleState.isALlEmpty()) {
       //设定电梯退出
  }

   //读取新输入的请求
   if (requestList.isEmpty() && requests.isEmpty() && eleState.isALlEmpty()) {
       try {
           requestList.wait();
      } catch (InterruptedException e) { /*nothing*/ }
  }
   while (!requestList.isEmpty()) {
      ......
  }
}
synchronized (eleState) {
  ....... //电梯调度
   eleState.awaken(); //转入电梯运行
   try { eleState.wait(); } catch (InterruptedException e) { /*nothing*/ }
}

Elevator中的锁

电梯运行时,需要改变电梯状态,锁EleState

synchronized (eleState) {
   //退出程序
   if (eleState.getState() == 0 && eleState.isCanInt()) {
       break;
  }

   this.move();    //电梯运行
   eleState.awaken();     //切换Manager进程
   try { eleState.wait(); } catch (InterruptedException e) { /*nothing*/ }
}

 

第二次作业

同步块

RequestList是输入进程InputHandler和调度器进程Scheduler之间的临界区,主要用于存储输入的请求。

ElevatorCache是调度器进程Scheduler和电梯进程Elevator之间的临界区,主要用于存储电梯的状态。

InputHandler中的锁

向RequestList中插入请求时,锁RequestList

while (true) {
   request = elevatorInput.nextRequest();
   synchronized (RequestList.getIt()) {
       if (request == null) {      //指令输入结束
           RequestList.getIt().setNoMoreInput();
           RequestList.getIt().notifyAll();
           break;
      } else {                    //指令传入RequestList
           RequestList.getIt().offer(request);
           RequestList.getIt().notifyAll();
      }
  }
}

Scheduler中的锁

从RequestList中读取请求时,锁RequestList

向ElevatorCache中插入乘客时,锁ElevatorCache

synchronized (RequestList.getIt()) {    //读取输入请求
  ...... //退出程序
   if (!RequestList.getIt().isNoMoreInput() &&
       RequestList.getIt().isEmpty() && requests.isEmpty()) {
       try {
           RequestList.getIt().wait();
      } catch (InterruptedException e) { /*nothing*/ }
  }
   //读取,直到RequestList为空
   while (!RequestList.getIt().isEmpty()) {
      ......
  }
}   //synchronized_END
elevatorCache = elevatorCaches.get(minAddress);
synchronized (elevatorCache) {
   PersonRequest request1 = (PersonRequest) request;
   elevatorCache.offerPerson(
       new Person(
           request1.getPersonId(), request1.getFromFloor(),
           request1.getToFloor()));
   elevatorCache.notifyAll();
}

Elevator中的锁

电梯调用ElevatorCache时,锁ElevatorCache

synchronized (cache) {
   if (cache.isCanInt() && cache.isEmpty()
       && floor == destination && persons.isEmpty()) {
       return;
  } else if (cache.isEmpty() && floor == destination && persons.isEmpty()) {
       try {
           cache.wait();
      } catch (InterruptedException e) { /*nothing*/ }
  }
}

 

第三次作业

同步块(与第二次作业相同)

RequestList是输入进程InputHandler和调度器进程Scheduler之间的临界区,主要用于存储输入的请求。

ElevatorCache是调度器进程Scheduler和电梯进程Elevator之间的临界区,主要用于存储电梯的状态。

InputHandler中的锁

向RequestList中插入请求时,锁RequestList

while (true) {
   request = elevatorInput.nextRequest();
   synchronized (RequestList.getIt()) {
       if (request == null) {      //指令输入结束
           RequestList.getIt().setNoMoreInput();
           RequestList.getIt().notifyAll();
           break;
      } else {                    //指令传入RequestList
           RequestList.getIt().offer(request);
           RequestList.getIt().notifyAll();
      }
  }
}

Scheduler中的锁

从RequestList中读取请求时,锁RequestList

向ElevatorCache中插入乘客时,锁ElevatorCache

synchronized (RequestList.getIt()) {    //读取输入请求
   if (RequestList.getIt().isNoMoreInput() &&
       RequestList.getIt().isEmpty() && requests.isEmpty()) {
       //结束全部Scheduler、InputHandler和Elevator
  }

   if (RequestList.getIt().isEmpty() && requests.isEmpty()) {
       try {
           RequestList.getIt().wait();
      } catch (InterruptedException e) { /*nothing*/ }
  }

   while (!RequestList.getIt().isEmpty()) {
      ......
  }
}   //synchronized_END
if (fitElevatorC(from, destination)) {
   synchronized (elevatorCaches[2]) {
       elevatorCaches[2].offerPerson(request);
       elevatorCaches[2].notifyAll();
  }
} else if (from % 2 == 1 && destination % 2 == 1) {
   synchronized (elevatorCaches[1]) {
       elevatorCaches[1].offerPerson(request);
       elevatorCaches[1].notifyAll();
  }
} else {
   synchronized (elevatorCaches[0]) {
       elevatorCaches[0].offerPerson(request);
       elevatorCaches[0].notifyAll();
  }
}

Elevator中的锁

电梯调用ElevatorCache时,锁ElevatorCache

synchronized (cache) {
   //退出程序:canInt表示Schedule已结束
   if (cache.isCanInt()) {
       return;
  } else if (cache.isEmpty() && floor == destination
              && persons.isEmpty() && waitings.isEmpty()) {
       synchronized (RequestList.getIt()) {
           RequestList.getIt().notifyAll();
      }
       try {
           cache.wait();
      } catch (InterruptedException e) { /*nothing*/ }
  }
}

 

二、调度器设置

第一次作业

调度器Manager用于读取RequestList和写EleState这两个临界区,Manager和Elevator两个进程依次运行。

第二次作业

调度器Scheduler用于读RequestList和分配请求给不同ElevatorCache这些临界区。

第三次作业

调度器Scheduler用于读RequestList和将乘客引导到不同类型的电梯的等候区ElevatorCache中。

 

三、第三次作业架构

第一次作业UML

第一次作业UML协作图

进程交互

Input进程

Manager进程

Elevator进程

 

第二次作业UML

第二次作业UML协作图

进程交互

InputHandler进程

Scheduler进程

Elevator进程

 

第三次作业UML

第三次作业UML协作图

进程交互

相比第二次作业,增加了换乘功能

InputHandler进程和电梯创建过程

Scheduler进程

ElevatorA(ElevatorB和ElevatorC类似)进程

 

第三次作业可扩展性分析

第三次作业的程序架构有较好的可扩展性。

(1)对于不同的电梯,应用工厂模式来生成不同的电梯子类,能支持功能不同的电梯的扩展。

(2)支持乘客换乘,对于不同的乘客需求,可支持不同换乘调度的扩展。

(3)可在调度器Scheduler中方便地加入不同调度策略。

 

四、自己程序BUG

第一次作业出现的bug是,调度器在一种特殊的情况下会发生电梯超员的情况,原因是Scheduler中调度算法中一个循环的判断条件没有判断电梯内人数情况。课下测试程度不够,没有覆盖到可能会出现问题的情况。

第三次作业出现的bug是电梯等待列表中有乘客,但电梯却保持不运行,但线程没有进入休眠,导致轮询。轮询产生的位置在ElevatorA.run()中。导致该问题的代码,在Elevator.getOn(),即电梯上乘客的方法中。我设置了一个waitings的List和一个persons的List,分别表示等待上电梯的乘客和已经在电梯中的乘客。这个问题的原因在于,当电梯内乘客persons为空时,但等待电梯的乘客waitings不为空时,电梯没有运行去接waitings中的乘客,且由于waitings不为空,电梯线程不进入休眠,导致轮询产生。解决方法是增加一个对该种情况的处理流程。

 

五、寻找程序BUG

首先通过自动评测机找到错误数据点,再基于该数据点进行手动测试。通过删减和增加数据内容,定位程序bug产生原因。该策略能够找到一些随机测试能覆盖到的bug,且比较有效。对于一些进程安全问题,也能够有效的发现,特别是死锁问题。

另外,我还手动构造一些临界数据,去测试程序的“抗压”能力。但并没有成功hack到。

本单元的测试策略和第一单元不尽相同。主要在于多线程的测试,有时候需要多次测试,才能确定由线程安全产生的bug是否存在。而第一单元每次程序执行的结果都是确定的,不需要反复测试同一数据点。

 

六、心得体会

在本次作业中,我对于线程知识有了一定的了解,能够根据需求去较为合理的划分线程,并且组织线程间的交互。在此设计过程中,线程安全问题是关键。若把线程间的临界区设置过小,可能不能保证线程的安全性;而把临界区设置过大,一定会导致程序的效率低下。我的解决方法是把临界区设为一个class实例,在class中尽量精简,这样就能兼顾安全和效率,比较有效。

在这次作业中,层次化设计依然十分重要。层次化设计贯穿了第一单元和第二单元作业,层次化设计的不足会影响程序的实现难度,并且也大概率会影响到下一次作业的迭代过程。通过本单元的学习,我进一步加深了对层次化设计的理解,相比第一单元已经能更合理地划分层次和实现需求。但我仍然需要进一步加深学习,尽可能做到更好。

此外,在本单元出现的bug较多,这令我意识到测试的重要性。所谓二八分原则,二成的时间用于实现代码,八成的时间用于防错,不是没有道理的o(╥﹏╥)o。

 posted on 2021-04-25 10:44  Junly7  阅读(85)  评论(0)    收藏  举报