301
社区成员
发帖
与我相关
我的任务
分享在本单元中第一次接触到多线程程序,因此在开始作业之前花了很长一段时间来学习多线程、互斥锁等内容。
在单线程程序执行时,同一时刻只有一段代码在执行,且属性仅由该线程使用,而多线程则是同时多个线程执行,在java中可以通过继承Thread类或者实现Runnable类接口来实现多线程,多线程运行过程中可能会同时访问相同对象,这时不得不引出线程互斥问题:如果某一线程正在对共享对象执行某种操作时,那么其他所有线程都不能进行对该共享对象执行操作。
生产者-消费者模式:生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
实现方法:使用 synchronized和wait、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(); // 释放写锁
}
}

WaitList整体请求队列,以及每个电梯内部的processingQueue等待队列和passengers乘客队列。WaitQueue,结束时设置其isEnd。WaitQueue中的请求根据调度策略分配到电梯内的processingQueue。
所有电梯均可见总请求队列(WaitList),每部电梯内置自己的等待队列(processingQueue),通过调度器(第二次作业加入)将请求队列中的请求加到调度电梯的等待队列当中。
电梯内有乘客时执行捎带策略,即LOOK策略:
电梯捎带是LOOK算法,即遵循电梯内请求为主请求,捎带过路者的策略。当路过一层楼时,如果电梯内有乘客到站,就开门让其下电梯;如果该层有同电梯运动方向相同的请求且电梯内人没有满,就开门让其进入;如果不符合以上两点,并且电梯内有人或者没人但同方向有请求,就按原方向移动;如果不符合以上三点,并且电梯内没人,则电梯掉头,并重新判断同方向是否有新请求。除此之外的情况,电梯进入等待状态。
当电梯经过某一楼层时,如果有乘客在电梯内要到达该楼层,则开门让他们下电梯。
如果该楼层有与电梯当前运动方向相同的请求,并且电梯内还有空间,则开门让这些乘客进入。
如果电梯内有乘客,或者没有乘客但是同方向有新的请求,则按照原先的运动方向移动。
如果电梯内没有乘客,并且当前方向上也没有新的请求,则电梯会改变方向,并重新检查是否有新的请求。
除了以上情况,电梯将进入等待状态。
RESET对于电梯重置则是在电梯接收到重置指令后尽快停靠,放下乘客完成重置动作后再投入电梯系统运行(修改其性能参数(满载人数、移动时间))。起初我把它理解为,当接收到RESET指令,结束该电梯线程,并重新实例化一个具有RESET参数的新电梯线程,但是后面发现当调度线程结束后,实例化新电梯的操作不易实现。于是就没有将重置电梯线程结束,而是让其sleep(Treset)并修改参数,继而投入使用。
isReset标记电梯是否正在重置过程中,使调度器跳过该电梯。getFromFloor为当前楼层,并将等待队列重新加入到总请求队列中。InputThread线程中输入为空时,且要求没有电梯正在重置时,设置isEnd。 RECEIVE约束RESEIVE,需注意电梯重置完成后再将其中请求进行分配并RECEIVE。 DCElevatorOccupied对象的锁,保证不同时进入换乘楼层。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主要集中在 TLE 问题上。
wait而不是continue轮询。synchronized嵌套顺序不一致造成死锁。synchronized,对于读取或修改共享对象的方法需要加,否则可能造成死锁。synchronized要保证嵌套次序一致。notifyAll的用法是唤醒wait中的线程,避免在synchronized语句块中滥用notifyAll方法,导致唤醒没有wait的线程造成CTLE。