Java Object wait(long timeout) 方法(实战总结)

Java Object wait(long timeout) 方法详解:线程同步中的耐心等待

在多线程编程中,我们常常需要让一个线程暂停执行,直到某个特定条件满足。Java 提供了 Object 类中的 wait(long timeout) 方法,作为线程间通信与协调的核心工具之一。它不像 sleep() 那样单纯地“休息”,而是真正地“等待某个事件发生”。这篇文章将带你深入理解 Java Object wait(long timeout) 方法 的工作原理、使用场景和常见陷阱。


理解 wait() 的基本概念

wait(long timeout)java.lang.Object 类提供的一个实例方法,它使当前线程进入等待状态,直到以下三种情况之一发生:

  1. 其他线程调用了该对象的 notify()notifyAll() 方法;
  2. 等待时间超时(即 timeout 参数指定的时间到达);
  3. 当前线程被中断(抛出 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 会:

  1. 释放当前线程持有的对象锁;
  2. 将线程放入该对象的“等待队列”;
  3. 将线程状态改为 WAITINGTIMED_WAITING
  4. 在超时或被 notify 后,线程重新尝试获取锁并恢复执行。

这整个过程是原子性的,保证了线程安全。


与 Thread.sleep(long millis) 的对比

特性 wait(long timeout) sleep(long millis)
是否需要同步块 ✅ 必须在 synchronized 中调用 ❌ 无需
是否释放锁 ✅ 释放当前对象锁 ❌ 不释放任何锁
唤醒方式 notify/notifyAll 或超时 仅超时
适用场景 线程间通信、条件等待 简单延迟、暂停执行

🌟 总结:如果只是“休息一下”,用 sleep();如果要“等某个条件”,用 wait(long timeout)


实际应用场景总结

  1. 生产者-消费者模型:缓冲区满/空时,生产者/消费者等待。
  2. 资源池管理:连接池中无可用连接时,线程等待。
  3. 任务调度:定时任务等待某事件发生。
  4. 并发队列:阻塞队列在空时等待元素入队。

这些场景中,Java Object wait(long timeout) 方法 是实现阻塞式等待的核心机制。


结语

Java Object wait(long timeout) 方法 是 Java 多线程编程中不可或缺的工具。它不仅仅是一个“暂停”命令,更是一种基于条件的线程协作机制。掌握它的使用,意味着你已迈入多线程编程的进阶门槛。

在实际开发中,务必注意同步块的使用、循环检查条件、中断处理和合理的超时设置。只有这样,才能写出既高效又安全的并发代码。

希望这篇文章能帮你彻底理解 Java Object wait(long timeout) 方法 的本质与用法,让你在多线程世界中游刃有余。