上一篇
java中等待怎么解除
- 后端开发
- 2025-09-08
- 23
Java中,可通过调用
notify()唤醒单个等待线程或
notifyAll()唤醒所有
等待线程来解除等待状态,这些方法需在同步代码块中使用
Java中,线程的等待(wait())和唤醒(notify()/notifyAll())是通过对象监视器机制实现的,当一个线程调用某个对象的wait()方法时,它会释放该对象的锁并进入等待状态;而其他线程可以通过调用同一对象的notify()或notifyAll()来解除这种等待,以下是详细的原理、使用方式及注意事项:
核心机制解析
- 必须配合同步块使用:无论是调用
wait()还是notify()/notifyAll(),都必须位于synchronized代码块内,这是因为这些方法依赖于对象内部的监视器锁(Monitor Lock),如果未获取锁就直接调用,会抛出IllegalMonitorStateException异常; - 状态转换过程:当线程A执行
obj.wait()后,它会被加入对象的等待队列,同时释放对obj的独占权,此时若有另一个线程B获得该锁并执行了obj.notify()或obj.notifyAll(),则线程A会被重新激活,尝试再次竞争锁以继续执行后续代码; - 两种唤醒模式差异:
notify()仅随机选择一个等待中的线程进行唤醒(适合单消费者场景),而notifyAll()会唤醒所有正在此对象上等待的线程(适用于多消费者并发处理的情况)。
典型实现步骤与示例代码
生产者-消费者模型中的等待解除流程
| 角色 | 操作逻辑 | 关键方法调用 |
|---|---|---|
| 生产者 | 若缓冲区已满,则调用wait()暂停生产 |
synchronized(buffer){... buffer.wait();} |
| 消费者 | 若缓冲区为空,则调用wait()暂停消费 |
synchronized(buffer){... buffer.wait();} |
| 被唤醒条件 | 对方完成反向操作(如生产者存入数据后调用notifyAll()告知消费者可取走物品) |
buffer.notifyAll(); |
public class ProduceConsumerExample {
private List<String> list = new ArrayList<>();
private final int maxSize = 5; // 最大容量限制
// 生产者方法
public void put(String item) throws InterruptedException {
synchronized (list) {
while (list.size() == maxSize) { // 避免虚假唤醒需用while循环判断
list.wait(); // 队列满时等待
}
list.add(item);
System.out.println("添加元素:" + item);
list.notifyAll(); // 通知所有可能等待的线程(包括消费者和其他生产者)
}
}
// 消费者方法
public String take() throws InterruptedException {
synchronized (list) {
while (list.isEmpty()) { // 同样需要while防虚假唤醒
list.wait(); // 队列空时等待
}
String val = list.remove(0);
System.out.println("取出元素:" + val);
list.notifyAll(); // 唤醒潜在生产者或其他消费者
return val;
}
}
}
️ 重要细节:必须使用while而非if检查条件!因为可能存在多个线程交替唤醒的情况(即“虚假唤醒”),此时即使没有被显式通知也可能意外退出等待状态。
高级技巧与扩展方案
- 超时控制:通过带参数版本的
wait(long millis)实现定时自动恢复,例如设置最长等待时间为10秒:list.wait(10000L);,这能有效避免死锁风险; - LockSupport替代方案:对于更复杂的调度需求,可以使用JDK提供的
LockSupport类的park()/unpark()方法,其优势在于不需要基于特定对象的监视器锁,适合跨线程直接通信的场景; - 中断处理优化:当线程处于等待状态时收到中断信号(如调用
Thread.interrupt()),会立即抛出InterruptedException并退出阻塞,开发者应在捕获此异常后做好资源清理工作。
常见错误规避指南
| 错误类型 | 表现 | 解决方案 |
|---|---|---|
| 未在同步块中调用wait | 运行时抛出IllegalMonitorStateException |
确保所有wait/notify操作都在synchronized块内 |
| 遗漏循环判断条件 | 因虚假唤醒导致提前结束等待 | 改用while循环替代if语句进行条件校验 |
| 过度使用notify | 多个线程同时被唤醒造成性能浪费 | 根据业务特点选择notify(单线程)或notifyAll(多线程) |
| 忘记释放锁 | 导致后续线程无法获取锁进而永久阻塞 | 确保在wait前已经释放锁,且只在持有锁时调用notify |
相关问答FAQs
Q1: 如果只用if而不是while来判断等待条件会发生什么?
答:可能会出现“虚假唤醒”(Spurious Wakeup),即线程并未被其他线程通过notify唤醒,而是由于系统调度等原因自动从wait状态返回,此时若使用if判断,程序将错误地认为条件已满足并继续执行,可能导致数据不一致等问题,因此必须用while循环确保只有在真实满足条件时才退出等待。
Q2: 为什么有时候需要调用notifyAll而不是notify?
答:当存在多个线程在同一对象上等待不同资源时(例如既有生产者又在等待空间可用,又有消费者在等待数据到来),仅调用notify()无法保证所有必要线程都被唤醒,此时应使用notifyAll()确保所有等待线程都能获得机会检查自己的条件是否达成,从而避免饥饿现象,不过频繁使用notifyAll可能影响性能,需
