目录

1. 完整的 Java 代码演示 (Producer-Consumer 模型)

2. 图片涉及知识点详细解析

7.1 wait() 方法

7.2 notify() 方法

7.3 notifyAll() 方法

7.4 wait 和 sleep 的对比 (高频面试题)

3. 详细扩展说明

A. 为什么 wait() 必须在 while 循环中调用?

B. IllegalMonitorStateException

C. 现代替代方案:Lock 和 Condition

下一步建议


这张图片展示的是 Java 多线程编程 中关于 线程通信(Thread Communication) 的核心知识点大纲,特别是围绕 Object 类提供的 wait()notify() 机制。

根据您的要求,我将编写一段完整的 Java 代码来演示这些概念,逐行注释,然后详细解析图片中的知识点以及 waitsleep 的经典面试题对比。


1. 完整的 Java 代码演示 (Producer-Consumer 模型)

为了演示 wait()notify()notifyAll(),最经典的场景是生产者-消费者模型。

Java

package com.example.thread;

// 定义一个简单的共享资源类
class SharedResource {
    // 这是一个共享的数据变量
    private String message;
    // 这是一个标志位,true 表示有数据(Wait for consumer),false 表示没数据(Wait for producer)
    private boolean hasMessage = false;

    // 7.1 & 7.3: 生产数据的方法
    public synchronized void produce(String newMessage) {
        // 使用 while 循环而不是 if,防止“虚假唤醒”(Spurious Wakeup)
        while (hasMessage) { 
            try {
                // 打印当前状态,表示生产者需要等待
                System.out.println("生产者: 队列已满,等待消费者消费...");
                // 7.1 wait()方法:释放锁,当前线程进入等待状态
                wait(); 
            } catch (InterruptedException e) {
                // 处理线程中断异常
                Thread.currentThread().interrupt();
                e.printStackTrace();
            }
        }
        
        // 模拟生产数据的过程
        this.message = newMessage;
        this.hasMessage = true;
        System.out.println("生产者: 已生产消息 -> " + newMessage);

        // 7.2 / 7.3 notify() 或 notifyAll() 方法:唤醒等待的线程
        // notify() 随机唤醒一个,notifyAll() 唤醒所有。这里演示 notify()
        notify(); 
    }

    // 消费数据的方法
    public synchronized void consume() {
        // 如果没有消息,消费者需要等待
        while (!hasMessage) {
            try {
                // 打印当前状态
                System.out.println("消费者: 队列为空,等待生产者生产...");
                // 7.1 wait()方法:释放锁,暂停执行
                wait(); 
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                e.printStackTrace();
            }
        }

        // 模拟消费数据的过程
        System.out.println("消费者: 已消费消息 -> " + this.message);
        this.hasMessage = false;

        // 唤醒生产者线程
        notify(); 
    }
}

// 测试主类
public class WaitNotifyDemo {
    public static void main(String[] args) {
        // 创建共享资源对象
        SharedResource resource = new SharedResource();

        // 创建生产者线程
        Thread producerThread = new Thread(() -> {
            // 生产 3 条数据
            String[] messages = {"消息A", "消息B", "消息C"};
            for (String msg : messages) {
                resource.produce(msg);
                try {
                    // 模拟生产耗时,sleep 不会释放锁,但这里是在同步块之外调用的
                    Thread.sleep(1000); 
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Producer-Thread");

        // 创建消费者线程
        Thread consumerThread = new Thread(() -> {
            // 消费 3 条数据
            for (int i = 0; i < 3; i++) {
                resource.consume();
                try {
                    // 模拟消费耗时
                    Thread.sleep(1000); 
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Consumer-Thread");

        // 启动线程
        producerThread.start();
        consumerThread.start();
    }
}

2. 图片涉及知识点详细解析

图片中的大纲涵盖了 Java 线程间通信的基础机制。所有这些方法都属于 java.lang.Object 类,而不是 Thread 类,这意味着任何对象都可以作为锁(Monitor)。

7.1 wait() 方法

  • 定义: 使当前线程立即停止运行,释放对象锁,并进入等待队列(Wait Set)。

  • 前提条件: 调用 wait() 的线程必须拥有该对象的监视器锁(Monitor),即必须在 synchronized 代码块或方法中调用。

  • 核心行为:

    1. 释放锁: 这是与 sleep() 最关键的区别。

    2. 阻塞: 线程进入 WAITINGTIMED_WAITING 状态。

    3. 等待唤醒: 直到其他线程调用同一个对象的 notify()notifyAll()

7.2 notify() 方法

  • 定义: 唤醒在此对象监视器上等待的单个线程。

  • 随机性:如果有多个线程在等待,选择唤醒哪一个是任意的(取决于具体的 JVM 实现),开发者无法指定唤醒哪一个。

  • 锁的持有: 调用 notify() 后,当前线程不会立即释放锁,而是要等到执行完当前的 synchronized 代码块后才释放。被唤醒的线程也要等到获得锁之后才能继续执行。

7.3 notifyAll() 方法

  • 定义: 唤醒在此对象监视器上等待的所有线程。

  • 行为: 所有等待的线程都会被唤醒,并开始竞争锁。只有一个线程能抢到锁并执行,其他的线程会再次进入阻塞状态(Blocked),等待锁被释放。

  • 使用场景: 当有多个线程等待,且你不确定应该唤醒哪一个,或者多个线程等待的条件可能同时满足时,使用 notifyAll() 更安全,可以避免“信号丢失”导致的死锁。

7.4 waitsleep 的对比 (高频面试题)

这是 Java 面试中最常见的问题之一。

特性 Object.wait() Thread.sleep()
所属类 Object Thread
锁的释放 释放锁 (Release Lock) 不释放锁 (Hold Lock)
使用范围 必须在 synchronized 块中 任何地方
用途 线程间通信 (Thread Communication) 暂停执行 (Pause Execution)
唤醒方式 需要 notify() / notifyAll() (除非设置超时) 时间到了自动唤醒
异常 抛出 InterruptedException 抛出 InterruptedException

一句话总结: wait 是用来让线程之间协作的(交出控制权和锁),而 sleep 只是让线程休息一下(不交出锁)。


3. 详细扩展说明

为了深入理解,我们需要补充几个图里没写但非常重要的点:

A. 为什么 wait() 必须在 while 循环中调用?

在上面的代码中,我使用了:

Java

while (condition) { 
    wait(); 
}

而不是:

Java

if (condition) { 
    wait(); 
}

原因: 防止虚假唤醒 (Spurious Wakeup)

  • 在底层操作系统层面,线程有时会在没有收到 notify 信号的情况下莫名其妙地醒来。

  • 如果使用 if,线程醒来后会直接往下执行,而不会再次检查条件是否满足,这可能导致程序逻辑错误(例如在队列为空时强行消费)。

  • 使用 while,线程醒来后会再次检查条件,如果条件不满足,它会再次调用 wait() 继续等待。

B. IllegalMonitorStateException

如果你在没有 synchronized 的代码块中调用 wait()notify(),程序会抛出 IllegalMonitorStateException 运行时异常。这是因为 Java 强制要求线程通信必须建立在持有锁的基础上。

C. 现代替代方案:Lock 和 Condition

虽然 wait/notify 是基础,但在 Java 5 引入 java.util.concurrent 包后,通常推荐使用更高级的 ReentrantLockCondition

  • synchronized $\rightarrow$ ReentrantLock

  • wait() $\rightarrow$ Condition.await()

  • notify() $\rightarrow$ Condition.signal()

优势: Condition 允许通过创建多个 Condition 对象(例如 notFullnotEmpty),实现精准唤醒某类线程,而不是像 notifyAll 那样粗暴地唤醒所有线程。

下一步建议

您是否希望我把上面的 wait/notify 代码改写成使用 ReentrantLock 和 Condition 的版本,以便您对比新旧两种写法的区别?

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐