Java Object wait(long timeout) 方法详解:线程同步中的耐心等待
在多线程编程中,我们常常需要让一个线程暂停执行,直到某个特定条件满足。Java 提供了 Object 类中的 wait(long timeout) 方法,作为线程间通信与协调的核心工具之一。它不像 sleep() 那样单纯地“休息”,而是真正地“等待某个事件发生”。这篇文章将带你深入理解 Java Object wait(long timeout) 方法 的工作原理、使用场景和常见陷阱。
理解 wait() 的基本概念
wait(long timeout) 是 java.lang.Object 类提供的一个实例方法,它使当前线程进入等待状态,直到以下三种情况之一发生:
- 其他线程调用了该对象的
notify()或notifyAll()方法; - 等待时间超时(即
timeout参数指定的时间到达); - 当前线程被中断(抛出
InterruptedException)。
📌 关键点:调用
wait()必须在同步块(synchronized block)中进行,否则会抛出IllegalMonitorStateException。
想象一下,你在餐厅等餐。服务员告诉你:“菜要 5 分钟,我等好了会喊你。” 这个“等”不是无意义地干坐,而是你把“等待”这件事托付给了服务员(即对象锁)。只有服务员(notify)喊你,或者时间到了(timeout),你才会醒来继续行动。
Java Object wait(long timeout) 方法的语法与参数说明
public final void wait(long timeout) throws InterruptedException
| 参数名 | 类型 | 说明 |
|---|---|---|
timeout |
long |
等待的最长时间(单位:毫秒) 若为 0,表示无限等待,直到被 notify/notifyAll 或中断 |
⚠️ 注意:
timeout为负数时会抛出IllegalArgumentException。
示例代码:使用 wait(long timeout) 实现限时等待
public class WaitExample {
private final Object lock = new Object();
private boolean dataReady = false;
public void waitForData() {
synchronized (lock) {
try {
// 等待最多 3000 毫秒(3 秒),直到 dataReady 变为 true
System.out.println("线程 " + Thread.currentThread().getName() + " 开始等待数据...");
lock.wait(3000); // 调用 Java Object wait(long timeout) 方法
// 如果没有被唤醒或超时,会继续执行下面的代码
if (dataReady) {
System.out.println("数据已准备就绪,继续处理...");
} else {
System.out.println("等待超时,数据未就绪,放弃处理。");
}
} catch (InterruptedException e) {
// 线程被中断时的处理逻辑
System.out.println("等待被中断,线程 " + Thread.currentThread().getName() + " 退出。");
Thread.currentThread().interrupt(); // 恢复中断状态
}
}
}
public void setDataReady() {
synchronized (lock) {
dataReady = true;
System.out.println("数据已准备,唤醒等待的线程...");
lock.notify(); // 唤醒一个正在等待的线程
}
}
public static void main(String[] args) {
WaitExample example = new WaitExample();
// 启动一个线程等待数据
Thread waiter = new Thread(example::waitForData, "Waiter");
waiter.start();
// 模拟延迟,让 wait 先执行
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 主线程设置数据并通知
example.setDataReady();
}
}
输出结果示例:
线程 Waiter 开始等待数据...
数据已准备,唤醒等待的线程...
数据已准备就绪,继续处理...
✅ 说明:由于
setDataReady()在wait开始后 1 秒执行,未超过 3 秒,因此线程被成功唤醒,没有超时。
wait(long timeout) 与 notify/notifyAll 的协同机制
wait(long timeout) 并不独立存在,它必须与 notify() 和 notifyAll() 配合使用,形成经典的“生产者-消费者”模式。
为什么需要 notifyAll()?
在多线程环境下,可能有多个线程在等待同一个对象锁。如果只调用 notify(),只会唤醒一个线程,而其他线程可能永远等不到通知。此时 notifyAll() 更安全,因为它会唤醒所有正在等待的线程。
示例:使用 notifyAll() 保证公平性
public class ProducerConsumer {
private final Object lock = new Object();
private boolean hasData = false;
public void produce() {
synchronized (lock) {
System.out.println("生产者开始生产数据...");
hasData = true;
System.out.println("数据已生产,通知所有等待者...");
lock.notifyAll(); // 通知所有等待线程
}
}
public void consume() {
synchronized (lock) {
try {
// 等待最多 5000 毫秒,直到有数据
while (!hasData) {
System.out.println("消费者等待数据...(最多等待 5 秒)");
lock.wait(5000); // Java Object wait(long timeout) 方法
}
System.out.println("消费者获取到数据,开始处理。");
hasData = false;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("消费被中断。");
}
}
}
public static void main(String[] args) {
ProducerConsumer pc = new ProducerConsumer();
// 启动消费者线程
Thread consumer = new Thread(pc::consume, "Consumer");
consumer.start();
// 延迟 3 秒后生产数据
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
pc.produce();
}
}
💡 小贴士:在循环中使用
wait()是最佳实践。因为即使被唤醒,也可能因为其他线程修改了条件而失效,所以必须重新检查条件。
常见陷阱与最佳实践
陷阱 1:在非 synchronized 块中调用 wait()
// ❌ 错误示例
Object obj = new Object();
obj.wait(1000); // 抛出 IllegalMonitorStateException
✅ 解决方法:始终在
synchronized块中调用。
陷阱 2:忘记恢复中断状态
当 wait() 抛出 InterruptedException 时,必须手动恢复中断状态,否则上层逻辑可能无法感知中断。
try {
lock.wait(3000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // ✅ 恢复中断状态
}
陷阱 3:超时时间设置不合理
- 设置为 0:可能导致线程永远等待,引发死锁。
- 设置过短:可能在数据准备好前就超时,导致任务失败。
✅ 建议:根据业务场景合理设置超时时间,通常 1 ~ 5 秒是常见范围。
Java Object wait(long timeout) 方法的底层机制
wait(long timeout) 的实现依赖于 Java 虚拟机(JVM)对对象监视器(monitor)的管理。当线程调用 wait() 时,JVM 会:
- 释放当前线程持有的对象锁;
- 将线程放入该对象的“等待队列”;
- 将线程状态改为
WAITING或TIMED_WAITING; - 在超时或被
notify后,线程重新尝试获取锁并恢复执行。
这整个过程是原子性的,保证了线程安全。
与 Thread.sleep(long millis) 的对比
| 特性 | wait(long timeout) |
sleep(long millis) |
|---|---|---|
| 是否需要同步块 | ✅ 必须在 synchronized 中调用 | ❌ 无需 |
| 是否释放锁 | ✅ 释放当前对象锁 | ❌ 不释放任何锁 |
| 唤醒方式 | notify/notifyAll 或超时 | 仅超时 |
| 适用场景 | 线程间通信、条件等待 | 简单延迟、暂停执行 |
🌟 总结:如果只是“休息一下”,用
sleep();如果要“等某个条件”,用wait(long timeout)。
实际应用场景总结
- 生产者-消费者模型:缓冲区满/空时,生产者/消费者等待。
- 资源池管理:连接池中无可用连接时,线程等待。
- 任务调度:定时任务等待某事件发生。
- 并发队列:阻塞队列在空时等待元素入队。
这些场景中,Java Object wait(long timeout) 方法 是实现阻塞式等待的核心机制。
结语
Java Object wait(long timeout) 方法 是 Java 多线程编程中不可或缺的工具。它不仅仅是一个“暂停”命令,更是一种基于条件的线程协作机制。掌握它的使用,意味着你已迈入多线程编程的进阶门槛。
在实际开发中,务必注意同步块的使用、循环检查条件、中断处理和合理的超时设置。只有这样,才能写出既高效又安全的并发代码。
希望这篇文章能帮你彻底理解 Java Object wait(long timeout) 方法 的本质与用法,让你在多线程世界中游刃有余。