Object.wait() 是 Java 多线程编程中用于线程间通信(Inter-Thread Communication) 的核心方法之一。它使当前线程进入等待状态,并释放其持有的对象监视器(锁),直到其他线程调用同一对象的 notify()notifyAll() 方法将其唤醒。


一、核心概念

1. 什么是 wait()

  • wait()java.lang.Object 类的方法,所有 Java 对象都继承此方法。
  • 它用于实现线程协作,常用于生产者-消费者模型、任务调度等场景。
  • 调用 wait() 的线程会:
    1. 释放当前持有的对象锁。
    2. 进入该对象的等待集(Wait Set)
    3. 停止运行,直到被唤醒或中断。

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(),或确保唤醒正确线程

五、注意事项

  1. 永远使用 while 循环检查条件
    防止虚假唤醒和多线程竞争:

    while (!condition) {
        wait();
    }
    
  2. 优先使用 notifyAll()
    notify() 可能唤醒错误的线程,导致死锁。notifyAll() 更安全,JVM 会优化竞争。

  3. 处理 InterruptedException
    wait() 声明抛出 InterruptedException,必须捕获并处理:

    } catch (InterruptedException e) {
        Thread.currentThread().interrupt(); // 恢复中断状态
        return; // 或抛出 RuntimeException
    }
    
  4. 避免在字符串常量上 wait()
    字符串常量池可能导致多个地方持有同一对象锁,引发意外阻塞。

    // ❌ 危险
    synchronized ("lock") {
        "lock".wait();
    }
    
    // ✅ 正确:使用私有对象
    private final Object lock = new Object();
    
  5. 超时控制
    使用 wait(long timeout) 避免无限等待:

    lock.wait(5000); // 最多等待5秒
    

六、使用技巧

  1. 封装等待逻辑
    将复杂的等待/唤醒逻辑封装在工具类中。

  2. 结合 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();
    }
    
  3. 调试技巧
    使用 jstack 查看线程状态,确认线程是否处于 WAITING 状态。


七、最佳实践

实践 说明
✅ 使用私有对象作为锁 避免外部干扰
✅ 用 while 循环检查条件 防止虚假唤醒
✅ 处理中断异常 保持线程中断状态
✅ 优先使用 notifyAll() 避免唤醒丢失
✅ 设置超时时间 防止无限等待
✅ 文档化等待条件 提高代码可读性

八、总结

  • Object.wait() 是 Java 线程协作的基石。
  • 必须在 synchronized 块中调用,并配合 notify() / notifyAll() 使用。
  • 核心规则:释放锁 → 等待 → 被唤醒 → 重新竞争锁
  • 防止虚假唤醒:永远使用 while 循环
  • 新项目推荐使用 java.util.concurrent 包中的 LockCondition,API 更清晰、功能更强大。

📌 黄金法则
调用 wait() 前,必须持有锁;调用后,锁被释放;醒来后,必须重新获取锁。

✅ 掌握 wait(),是深入理解 Java 多线程协作的关键!