Java Object wait(long timeout, int nanos) 方法详解
在多线程编程的世界里,线程之间的协调与通信是核心问题之一。Java 提供了丰富的同步机制,其中 Object 类的 wait(long timeout, int nanos) 方法就是一种非常关键的阻塞式等待手段。它允许一个线程在特定条件下暂停执行,直到其他线程发出通知或超时时间到达。对于初学者来说,这个方法可能显得有些神秘,但一旦掌握其原理和使用场景,你会发现它在并发控制中有着不可替代的作用。
本文将带你一步步理解 wait(long timeout, int nanos) 方法的工作机制、实际应用场景以及常见陷阱,帮助你在开发中更安全、高效地使用这一机制。
方法签名与基本语义
wait(long timeout, int nanos) 是 Object 类中定义的一个实例方法,其完整签名如下:
public final void wait(long timeout, int nanos) throws InterruptedException
这个方法的作用是:让当前线程进入等待状态,直到以下三种情况之一发生:
- 其他线程调用了该对象的
notify()或notifyAll()方法; - 等待时间超时(由
timeout和nanos共同决定); - 当前线程被中断(抛出
InterruptedException)。
⚠️ 注意:调用此方法前,当前线程必须持有该对象的监视器锁(monitor),否则会抛出
IllegalMonitorStateException。这正是为什么它通常出现在synchronized块或方法中。
参数解析:timeout 与 nanos 的作用
这个方法有两个参数:
long timeout:等待的最长时间(单位:毫秒)int nanos:额外的纳秒数(0 ~ 999999999)
这两个参数合起来构成总等待时间。例如:
wait(5000, 500000000); // 等待 5 秒 + 500 毫秒 = 5.5 秒
实际计算规则
Java 中的 wait 方法会将 timeout 和 nanos 合并为总时间(以毫秒为单位):
- 如果
timeout为 0 且nanos为 0,则表示“无限等待”(直到被通知或中断); - 如果
timeout为 0 且nanos> 0,等价于等待nanos纳秒; - 如果
timeout> 0,则总时间 =timeout + (nanos + 999999) / 1000000毫秒(向上取整)。
✅ 小贴士:
nanos的值必须在 0 到 999,999,999 之间,否则会抛出IllegalArgumentException。
实际案例:生产者-消费者模型
为了更好地理解 wait(long timeout, int nanos) 方法的实际用途,我们来看一个经典的生产者-消费者场景。
假设有一个共享的缓冲区,生产者往里面放数据,消费者从中取出数据。当缓冲区为空时,消费者必须等待;当缓冲区满时,生产者必须等待。
public class ProducerConsumerDemo {
private final Object lock = new Object();
private final int[] buffer = new int[5];
private int count = 0;
private int in = 0;
private int out = 0;
// 生产者线程
public void produce(int value) {
synchronized (lock) {
while (count == buffer.length) {
try {
System.out.println("缓冲区已满,生产者等待...");
// 等待最多 3 秒,超时后继续尝试
lock.wait(3000, 0);
} catch (InterruptedException e) {
System.out.println("生产者被中断");
Thread.currentThread().interrupt();
return;
}
}
buffer[in] = value;
in = (in + 1) % buffer.length;
count++;
System.out.println("生产者生产:" + value + ",当前数量:" + count);
lock.notifyAll(); // 唤醒所有等待的消费者
}
}
// 消费者线程
public int consume() {
synchronized (lock) {
while (count == 0) {
try {
System.out.println("缓冲区为空,消费者等待...");
// 最多等待 2 秒
lock.wait(2000, 0);
} catch (InterruptedException e) {
System.out.println("消费者被中断");
Thread.currentThread().interrupt();
return -1;
}
}
int value = buffer[out];
out = (out + 1) % buffer.length;
count--;
System.out.println("消费者消费:" + value + ",剩余数量:" + count);
lock.notifyAll(); // 唤醒所有等待的生产者
return value;
}
}
// 测试主方法
public static void main(String[] args) {
ProducerConsumerDemo demo = new ProducerConsumerDemo();
Thread producer = new Thread(() -> {
for (int i = 1; i <= 10; i++) {
demo.produce(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
Thread consumer = new Thread(() -> {
for (int i = 0; i < 10; i++) {
demo.consume();
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
producer.start();
consumer.start();
try {
producer.join();
consumer.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
代码解析
synchronized(lock)确保线程对共享资源的互斥访问;while (count == 0)是关键:必须使用 while 而不是 if,因为wait()可能被虚假唤醒(spurious wakeup);lock.wait(2000, 0)设置了 2 秒的等待时间,如果在这段时间内没有被通知,就会自动恢复执行;notifyAll()唤醒所有等待线程,避免死锁。
常见误区与最佳实践
误区一:使用 if 替代 while
// ❌ 错误示例
if (count == 0) {
lock.wait(2000, 0);
}
问题:如果线程被虚假唤醒(即使没有被 notify),count 仍然为 0,但线程却继续执行,导致 consume() 返回错误数据。
✅ 正确做法:始终使用 while 条件判断。
误区二:忽略中断处理
wait() 会抛出 InterruptedException,这是线程中断机制的一部分。如果你忽略了它,程序可能无法响应外部中断请求。
try {
lock.wait(3000, 0);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 重新设置中断标志
return;
}
💡 重要:
Thread.currentThread().interrupt()是必须的,因为wait()会清除中断状态。
误区三:超时时间设置不合理
- 太短:频繁超时导致性能下降;
- 太长:系统响应变慢,用户体验差。
建议根据业务场景合理设置,例如:
| 场景 | 推荐超时时间 |
|---|---|
| 数据库连接等待 | 5000 ms |
| 网络请求等待 | 10000 ms |
| 缓冲区同步 | 2000 ~ 3000 ms |
与其他 wait 方法的对比
| 方法 | 说明 | 使用场景 |
|---|---|---|
wait() |
无限等待,直到被通知或中断 | 确保一定被唤醒的场景 |
wait(long timeout) |
等待指定毫秒数 | 一般超时控制 |
wait(long timeout, int nanos) |
精确到纳秒的等待 | 高精度同步需求 |
✅ 优先推荐使用
wait(long timeout, int nanos),因为它提供了更高的时间精度,尤其适合对时间敏感的应用。
为什么必须在 synchronized 块中调用?
这是因为 wait() 方法需要操作对象的监视器锁(monitor)。Java 的并发机制规定:只有持有锁的线程才能调用 wait、notify、notifyAll。
如果你在非同步上下文中调用:
lock.wait(1000, 0); // ❌ 抛出 IllegalMonitorStateException
会立即抛出异常,提示“当前线程未持有该对象的锁”。
总结与建议
Java Object wait(long timeout, int nanos) 方法 是 Java 并发编程中一个基础但极其重要的工具。它为线程间协作提供了可靠的等待与唤醒机制,尤其适合在资源竞争、缓冲区管理等场景中使用。
回顾关键点:
- 必须在
synchronized块中调用; - 使用
while循环检查条件,避免虚假唤醒; - 正确处理
InterruptedException; - 合理设置
timeout和nanos,避免超时过短或过长; - 优先使用
notifyAll()而非notify(),防止线程遗漏。
最佳实践总结表
| 项目 | 推荐做法 |
|---|---|
| 条件判断 | 使用 while 而非 if |
| 中断处理 | 捕获 InterruptedException,并调用 interrupt() |
| 超时设置 | 根据业务需求设置合理值(通常 2~5 秒) |
| 通知方式 | 使用 notifyAll() 保证所有等待线程被唤醒 |
| 代码结构 | 将 wait 与 notifyAll 放在 synchronized 块中 |
掌握 wait(long timeout, int nanos) 方法,是你迈向高级 Java 并发编程的重要一步。它不仅是技术工具,更是一种思维方式——学会如何让线程“有耐心地等待”,而不是盲目地“轮询”资源。
下次你在写多线程程序时,不妨想想:是否可以用这个方法来优雅地解决线程协调问题?答案很可能是“可以”。