wait 和 notify
约 512 字大约 2 分钟
2024-08-08
在 Java 程序中,synchronized 解决了多线程竞争的问题。例如,对于一个任务管理器,多个线程同时往队列中添加和获取任务,可以用 synchronized 加锁,但是并没有解决多线程协调的问题
class TaskQueue {
Queue<String> queue = new LinkedList<>();
public synchronized void addTask(String s) {
this.queue.add(s);
}
public synchronized String getTask() {
while (queue.isEmpty()) {
}
return queue.remove();
}
}上述代码看上去没有问题,但实际上 while() 循环永远不会退出。因为线程在执行 while() 循环时,已经在 getTask() 入口获取了 this 锁,其他线程根本无法调用 addTask(),因为 addTask() 执行条件也是获取 this 锁
如果深入思考一下,我们想要的执行效果是:
线程
1可以调用addTask()不断往队列中添加任务线程
2可以调用getTask()从队列中获取任务。如果队列为空,则getTask()应该等待,直到队列中至少有一个任务时再返回
因此,多线程协调运行的原则就是:当条件不满足时,线程进入等待状态;当条件满足时,线程被唤醒,继续执行任务
改造上述TaskQueue,
getTask()方法,在条件不满足时,线程进入等待状态addTask()方法,添加之后唤醒其他线程
class TaskQueue {
Queue<String> queue = new LinkedList<>();
public synchronized void addTask(String s) {
this.queue.add(s);
this.notifyAll();
}
public synchronized String getTask() throws InterruptedException {
while (queue.isEmpty()) {
this.wait();
}
return queue.remove();
}
}addTask() 方法内部调用了 this.notifyAll() 而不是 this.notify(),使用 notifyAll() 将唤醒所有当前正在 this 锁等待的线程,而 notify() 只会唤醒其中一个(具体哪个依赖操作系统,有一定的随机性)。这是因为可能有多个线程正在 getTask() 方法内部的 wait() 中等待,使用 notifyAll() 将一次性全部唤醒。通常来说,notifyAll() 更安全。有些时候,如果我们的代码逻辑考虑不周,用 notify() 会导致只唤醒了一个线程,而其他线程可能永远等待下去醒不过来了