Java Object wait(long timeout, int nanos) 方法(快速上手)

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

这个方法的作用是:让当前线程进入等待状态,直到以下三种情况之一发生:

  1. 其他线程调用了该对象的 notify()notifyAll() 方法;
  2. 等待时间超时(由 timeoutnanos 共同决定);
  3. 当前线程被中断(抛出 InterruptedException)。

⚠️ 注意:调用此方法前,当前线程必须持有该对象的监视器锁(monitor),否则会抛出 IllegalMonitorStateException。这正是为什么它通常出现在 synchronized 块或方法中。


参数解析:timeout 与 nanos 的作用

这个方法有两个参数:

  • long timeout:等待的最长时间(单位:毫秒)
  • int nanos:额外的纳秒数(0 ~ 999999999)

这两个参数合起来构成总等待时间。例如:

wait(5000, 500000000); // 等待 5 秒 + 500 毫秒 = 5.5 秒

实际计算规则

Java 中的 wait 方法会将 timeoutnanos 合并为总时间(以毫秒为单位):

  • 如果 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 的并发机制规定:只有持有锁的线程才能调用 waitnotifynotifyAll

如果你在非同步上下文中调用:

lock.wait(1000, 0); // ❌ 抛出 IllegalMonitorStateException

会立即抛出异常,提示“当前线程未持有该对象的锁”。


总结与建议

Java Object wait(long timeout, int nanos) 方法 是 Java 并发编程中一个基础但极其重要的工具。它为线程间协作提供了可靠的等待与唤醒机制,尤其适合在资源竞争、缓冲区管理等场景中使用。

回顾关键点:

  • 必须在 synchronized 块中调用;
  • 使用 while 循环检查条件,避免虚假唤醒;
  • 正确处理 InterruptedException
  • 合理设置 timeoutnanos,避免超时过短或过长;
  • 优先使用 notifyAll() 而非 notify(),防止线程遗漏。

最佳实践总结表

项目 推荐做法
条件判断 使用 while 而非 if
中断处理 捕获 InterruptedException,并调用 interrupt()
超时设置 根据业务需求设置合理值(通常 2~5 秒)
通知方式 使用 notifyAll() 保证所有等待线程被唤醒
代码结构 waitnotifyAll 放在 synchronized 块中

掌握 wait(long timeout, int nanos) 方法,是你迈向高级 Java 并发编程的重要一步。它不仅是技术工具,更是一种思维方式——学会如何让线程“有耐心地等待”,而不是盲目地“轮询”资源。

下次你在写多线程程序时,不妨想想:是否可以用这个方法来优雅地解决线程协调问题?答案很可能是“可以”。