Object.wait()
是 Java 多线程编程中用于线程间通信(Inter-Thread Communication) 的核心方法之一。它使当前线程进入等待状态,并释放其持有的对象监视器(锁),直到其他线程调用同一对象的 notify()
或 notifyAll()
方法将其唤醒。
一、核心概念
1. 什么是 wait()
?
wait()
是java.lang.Object
类的方法,所有 Java 对象都继承此方法。- 它用于实现线程协作,常用于生产者-消费者模型、任务调度等场景。
- 调用
wait()
的线程会:- 释放当前持有的对象锁。
- 进入该对象的等待集(Wait Set)。
- 停止运行,直到被唤醒或中断。
2. 相关方法
方法 | 说明 |
---|---|
void wait() |
无限等待,直到被 notify() / notifyAll() 唤醒 |
void wait(long timeout) |
等待指定毫秒数,超时自动唤醒 |
void wait(long timeout, int nanos) |
更精确的超时控制(毫秒 + 纳秒) |
void notify() |
唤醒等待集中一个线程(随机) |
void notifyAll() |
唤醒等待集中所有线程 |
二、核心规则(必须遵守!)
1. 必须在 synchronized
块中调用
wait()
、notify()
、notifyAll()
必须在同步代码块或方法中调用。- 因为它们操作的是对象的监视器(Monitor),只有持有锁的线程才能执行。
synchronized (obj) {
obj.wait(); // ✅ 正确
}
// obj.wait(); // ❌ 编译通过,但运行时抛出 IllegalMonitorStateException
2. 调用后释放锁
wait()
会立即释放当前线程持有的对象锁。- 其他线程可以获取该锁并进入同步块。
3. 唤醒后重新竞争锁
- 被唤醒的线程不会立即执行,而是进入锁竞争队列。
- 必须重新获取对象锁后才能继续执行
wait()
后的代码。
三、操作步骤(详细示例)
场景:生产者-消费者模型
public class ProducerConsumer {
private final Object lock = new Object();
private boolean hasData = false;
// 生产者线程
public void producer() {
synchronized (lock) {
while (hasData) { // 使用 while 而非 if(防止虚假唤醒)
try {
System.out.println("生产者等待...");
lock.wait(); // 释放锁,进入等待
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
// 生产数据
System.out.println("生产者生产数据");
hasData = true;
lock.notifyAll(); // 唤醒所有等待线程
}
}
// 消费者线程
public void consumer() {
synchronized (lock) {
while (!hasData) {
try {
System.out.println("消费者等待...");
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
// 消费数据
System.out.println("消费者消费数据");
hasData = false;
lock.notifyAll();
}
}
}
测试代码
public class Test {
public static void main(String[] args) {
ProducerConsumer pc = new ProducerConsumer();
Thread producer = new Thread(() -> {
for (int i = 0; i < 3; i++) {
pc.producer();
}
});
Thread consumer = new Thread(() -> {
for (int i = 0; i < 3; i++) {
pc.consumer();
}
});
consumer.start();
producer.start(); // 调整启动顺序可观察不同行为
}
}
预期输出:
消费者等待...
生产者生产数据
生产者等待...
消费者消费数据
消费者等待...
生产者生产数据
生产者等待...
消费者消费数据
消费者等待...
生产者生产数据
四、常见错误与解决方案
错误 | 原因 | 解决方案 |
---|---|---|
IllegalMonitorStateException |
未在 synchronized 块中调用 wait() |
确保在同步块内调用 |
死锁 | 线程永远等待,无人唤醒 | 确保有 notify() / notifyAll() 调用 |
虚假唤醒(Spurious Wakeup) | 线程无故被唤醒(JVM 允许) | 使用 while 循环检查条件,而非 if |
唤醒丢失(Lost Wakeup) | notify() 在 wait() 前调用 |
使用状态变量(如 hasData )确保逻辑正确 |
只唤醒一个线程导致死锁 | 使用 notify() 但多个线程等待 |
优先使用 notifyAll() ,或确保唤醒正确线程 |
五、注意事项
永远使用
while
循环检查条件
防止虚假唤醒和多线程竞争:while (!condition) { wait(); }
优先使用
notifyAll()
notify()
可能唤醒错误的线程,导致死锁。notifyAll()
更安全,JVM 会优化竞争。处理
InterruptedException
wait()
声明抛出InterruptedException
,必须捕获并处理:} catch (InterruptedException e) { Thread.currentThread().interrupt(); // 恢复中断状态 return; // 或抛出 RuntimeException }
避免在字符串常量上
wait()
字符串常量池可能导致多个地方持有同一对象锁,引发意外阻塞。// ❌ 危险 synchronized ("lock") { "lock".wait(); } // ✅ 正确:使用私有对象 private final Object lock = new Object();
超时控制
使用wait(long timeout)
避免无限等待:lock.wait(5000); // 最多等待5秒
六、使用技巧
封装等待逻辑
将复杂的等待/唤醒逻辑封装在工具类中。结合
Condition
使用(推荐)
java.util.concurrent.locks.Condition
提供了更灵活的等待/通知机制:Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); lock.lock(); try { while (!conditionMet) { condition.await(); // 等价于 wait() } // 处理逻辑 condition.signalAll(); // 等价于 notifyAll() } finally { lock.unlock(); }
调试技巧
使用jstack
查看线程状态,确认线程是否处于WAITING
状态。
七、最佳实践
实践 | 说明 |
---|---|
✅ 使用私有对象作为锁 | 避免外部干扰 |
✅ 用 while 循环检查条件 |
防止虚假唤醒 |
✅ 处理中断异常 | 保持线程中断状态 |
✅ 优先使用 notifyAll() |
避免唤醒丢失 |
✅ 设置超时时间 | 防止无限等待 |
✅ 文档化等待条件 | 提高代码可读性 |
八、总结
Object.wait()
是 Java 线程协作的基石。- 必须在
synchronized
块中调用,并配合notify()
/notifyAll()
使用。 - 核心规则:释放锁 → 等待 → 被唤醒 → 重新竞争锁。
- 防止虚假唤醒:永远使用
while
循环。 - 新项目推荐使用
java.util.concurrent
包中的Lock
和Condition
,API 更清晰、功能更强大。
📌 黄金法则:
“调用wait()
前,必须持有锁;调用后,锁被释放;醒来后,必须重新获取锁。”
✅ 掌握 wait()
,是深入理解 Java 多线程协作的关键!