Java Object notify() 方法(手把手讲解)

Java Object notify() 方法详解:线程协作的“唤醒信号”

在 Java 多线程编程中,线程之间的协作是构建高效、稳定并发程序的核心。当我们需要让一个线程等待某个条件满足后再继续执行时,wait()notify() 就成了必不可少的工具。今天我们就来深入聊聊 Java Object notify() 方法,它是实现线程间通信的重要机制之一。

想象一下,你在餐厅等位吃饭。服务员告诉你“菜好了就叫你”,你坐在位置上等待,直到服务员喊你。这个“叫你”的动作,就类似于 notify() 的作用——它通知一个或多个正在等待的线程,某个条件已经满足,可以继续执行了。

notify() 必须配合 wait() 使用,它们都属于 Object 类的方法,这意味着每个对象都可以作为锁对象,用于控制线程的等待与唤醒。


为什么需要 notify()?线程等待的场景解析

在并发编程中,我们经常遇到这样的场景:某个线程需要等待某个资源就绪,比如:

  • 缓冲区为空,消费者线程必须等待生产者放入数据;
  • 线程池中没有空闲线程,新任务必须等待;
  • 数据库连接池中的连接被占用,请求线程需要等待释放。

这些场景下,如果线程一直“忙等”(即不断循环检查条件),会严重浪费 CPU 资源。因此,Java 提供了 wait() 方法让线程主动释放锁并进入等待状态,直到被其他线程唤醒。

notify() 就是唤醒等待线程的关键。它不直接唤醒线程,而是通知一个正在等待该对象锁的线程,使其从 wait() 状态中恢复,重新尝试获取锁并继续执行。

⚠️ 注意:notify() 只能唤醒一个线程,如果多个线程在等待,Java 会随机选择一个。若要唤醒所有等待线程,应使用 notifyAll()


notify() 的使用前提:同步块与锁机制

notify() 不能随意调用,它必须在当前线程持有该对象的监视器锁(即 synchronized 锁)的情况下才能执行。否则会抛出 IllegalMonitorStateException

我们来看一个典型的使用场景:生产者-消费者模型。

public class ProducerConsumerExample {
    private final Object lock = new Object();
    private boolean isProduced = false;

    // 生产者线程
    public void produce() {
        synchronized (lock) {
            // 如果已经有产品,就等待
            while (isProduced) {
                try {
                    System.out.println("生产者:已有产品,等待消费...");
                    lock.wait(); // 释放锁并进入等待状态
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    System.out.println("生产者被中断");
                    return;
                }
            }

            // 模拟生产过程
            System.out.println("生产者:正在生产...");
            isProduced = true;
            System.out.println("生产者:产品已生产完成");

            // 唤醒等待的消费者
            lock.notify(); // 通知一个正在等待的线程
            System.out.println("生产者:已通知消费者");
        }
    }

    // 消费者线程
    public void consume() {
        synchronized (lock) {
            // 如果没有产品,就等待
            while (!isProduced) {
                try {
                    System.out.println("消费者:无产品,等待生产...");
                    lock.wait(); // 释放锁并等待
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    System.out.println("消费者被中断");
                    return;
                }
            }

            // 消费产品
            System.out.println("消费者:正在消费...");
            isProduced = false;
            System.out.println("消费者:产品已消费");

            // 唤醒等待的生产者
            lock.notify(); // 通知生产者
            System.out.println("消费者:已通知生产者");
        }
    }

    public static void main(String[] args) {
        ProducerConsumerExample example = new ProducerConsumerExample();

        Thread producer = new Thread(() -> example.produce());
        Thread consumer = new Thread(() -> example.consume());

        // 启动线程
        producer.start();
        consumer.start();

        try {
            producer.join();
            consumer.join();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

代码说明:

  • synchronized (lock) 确保了只有一个线程能进入临界区;
  • wait() 会释放 lock 锁,并让当前线程进入等待队列;
  • notify() 会唤醒一个在该锁上等待的线程(不是立即执行,而是进入“就绪队列”);
  • while 循环用于避免虚假唤醒(spurious wakeup),这是 Java 官方建议的写法。

notify() 与 notifyAll() 的区别:唤醒一个还是全部?

虽然 notify() 很常用,但它只唤醒一个等待线程。这在某些场景下可能导致“死锁”或“资源浪费”。

举个例子:假设你有 3 个消费者线程在等待,生产者只调用一次 notify(),那么只有一个消费者被唤醒,其他两个仍然在等待。如果下一个生产事件还没发生,那这 2 个消费者可能永远等不到通知。

因此,当多个线程对同一条件感兴趣时,推荐使用 notifyAll()。它会唤醒所有在该对象上等待的线程,让它们重新竞争锁。

方法 唤醒数量 适用场景
notify() 1 个 只有一个线程需要被唤醒
notifyAll() 所有等待线程 多个线程共享资源,需全部参与判断

✅ 建议:除非你明确知道只需要唤醒一个线程,否则优先使用 notifyAll(),更安全。


notify() 的执行时机与线程调度

notify() 的调用时机非常关键。它只是“发信号”,并不保证被唤醒的线程会立刻执行。因为:

  1. 被唤醒的线程需要重新获取锁;
  2. JVM 的线程调度器决定何时执行;
  3. 可能存在其他高优先级线程正在运行。

我们来修改上面的例子,加入线程优先级控制,观察 notify() 的行为:

public class NotifyTimingExample {
    private final Object lock = new Object();
    private boolean flag = false;

    public void signal() {
        synchronized (lock) {
            System.out.println("信号线程:准备唤醒等待线程");
            lock.notify(); // 发出唤醒信号
            System.out.println("信号线程:已发出通知");
            // 模拟信号发出后的延迟
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    public void waitAndCheck() {
        synchronized (lock) {
            System.out.println("等待线程:开始等待...");
            try {
                lock.wait(); // 释放锁,进入等待
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return;
            }
            System.out.println("等待线程:被唤醒,检查条件");
            System.out.println("等待线程:flag = " + flag);
        }
    }

    public static void main(String[] args) {
        NotifyTimingExample example = new NotifyTimingExample();

        Thread signalThread = new Thread(() -> example.signal());
        Thread waitThread = new Thread(() -> example.waitAndCheck());

        waitThread.setPriority(Thread.MIN_PRIORITY); // 设置优先级低
        signalThread.setPriority(Thread.MAX_PRIORITY); // 设置优先级高

        waitThread.start();
        try {
            Thread.sleep(500); // 让等待线程先进入 wait
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        signalThread.start();

        try {
            signalThread.join();
            waitThread.join();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

运行结果中你会看到:虽然 notify() 先执行,但等待线程仍需等待锁空闲后才能继续。这说明 notify() 只是“通知”,并非“立即执行”。


常见错误与最佳实践

在使用 Java Object notify() 方法 时,开发者常犯以下错误:

错误 1:在非 synchronized 块中调用 notify()

// ❌ 错误示例
lock.notify(); // 抛出 IllegalMonitorStateException

✅ 正确做法:必须在 synchronized 块中调用。

错误 2:用 if 替代 while 判断条件

// ❌ 错误
if (!isProduced) {
    lock.wait();
}
// 若发生虚假唤醒,线程会继续执行,导致逻辑错误

// ✅ 正确
while (!isProduced) {
    lock.wait();
}

错误 3:忘记调用 notify(),导致线程永远等待

这是最常见的死锁原因之一。建议在每个 wait() 的路径中,都确保有对应的 notify() 被执行。


总结:掌握 notify() 的核心要点

  • notify()Object 类的方法,用于唤醒一个正在等待该对象锁的线程;
  • 必须在 synchronized 块中调用,否则抛出异常;
  • wait() 配合使用,实现线程间的协作;
  • 使用 while 循环判断条件,防止虚假唤醒;
  • 优先考虑 notifyAll(),避免线程“饥饿”;
  • 唤醒后线程仍需竞争锁,不能立即执行。

掌握 Java Object notify() 方法,是你迈向高级并发编程的第一步。它虽然简单,但背后蕴含着线程调度、锁机制、资源竞争等复杂概念。理解它,不仅能写出更健壮的代码,还能在面试中游刃有余。

记住:线程之间的通信,不是靠“喊”,而是靠“信号”——notify() 就是那个关键的信号灯。