上一篇
java中等待怎么解除
- 后端开发
- 2025-09-08
- 6
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可能影响性能,需