Java多线程通信:wait/notify与sleep的深度剖析(时序图详解)

在Java多线程编程中,线程间的通信与协作是实现复杂并发逻辑的关键。wait()notify()以及sleep()方法作为线程控制的重要工具,有着各自独特的使用场景与规则。本文将深入探讨wait()notify()的协作机制,以及sleep()的阻塞特性,同时重点解析wait()必须在循环中调用的核心原因——防止虚假唤醒(Spurious Wakeup)。

一、wait/notify:线程间通信的桥梁

1.1 wait/notify的核心功能

wait()notify()是Object类的方法,主要用于线程间的协作与通信。

  • wait():当一个线程调用wait()方法时,它会释放当前持有的对象锁,并进入等待状态。直到其他线程调用同一个对象的notify()notifyAll()方法将其唤醒。
  • notify():随机唤醒一个在该对象上等待的线程;而notifyAll()则会唤醒所有在该对象上等待的线程。

关键操作时序图
Java多线程通信:wait/notify与sleep的深度剖析(时序图详解)_第1张图片

1.2 wait/notify的使用规则

wait()notify()必须在synchronized同步代码块或同步方法中使用,这是因为它们的操作依赖于对象锁的状态。若不在同步环境中调用,会抛出IllegalMonitorStateException异常。

1.3 典型使用场景:生产者-消费者模型

class SharedResource {
    private int data;
    private boolean isEmpty = true;

    public synchronized void produce(int value) {
        while (!isEmpty) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        data = value;
        isEmpty = false;
        notifyAll();
    }

    public synchronized void consume() {
        while (isEmpty) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("Consumed: " + data);
        isEmpty = true;
        notifyAll();
    }
}

在上述代码中,生产者线程在缓冲区满时调用wait()等待,消费者线程消费数据后通过notifyAll()唤醒生产者;反之亦然。

二、sleep:简单的线程阻塞

2.1 sleep的特性

sleep()是Thread类的静态方法,用于让当前线程暂停执行指定的时间(以毫秒为单位)。与wait()不同,sleep()不会释放线程持有的锁。

2.2 使用场景

  • 模拟延迟:在测试代码或需要控制执行节奏的场景中,通过sleep()让线程暂停一段时间。
  • 避免过度占用资源:例如在轮询检查某个条件时,加入sleep()可以减少CPU的占用。
public class SleepExample {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            synchronized (SleepExample.class) {
                try {
                    System.out.println("线程开始睡眠");
                    Thread.sleep(2000);
                    System.out.println("线程睡眠结束");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
    }
}

在上述代码中,线程在持有锁的情况下进行睡眠,期间其他线程无法获取该锁。

三、为什么wait()必须在循环中调用:防止虚假唤醒

3.1 什么是虚假唤醒

虚假唤醒(Spurious Wakeup)是指线程在没有被其他线程调用notify()notifyAll()的情况下被唤醒。虽然这种情况在实际中并不常见,但Java的wait()机制允许它发生,这是JVM底层实现的结果。

3.2 循环检查的必要性

如果不使用循环,当发生虚假唤醒时,线程会误以为等待的条件已经满足,继续执行后续代码,可能导致程序逻辑错误。因此,wait()必须配合while循环使用,每次被唤醒后重新检查条件是否真正满足。

synchronized (obj) {
    while (!condition) {
        obj.wait();
    }
    // 条件满足,执行相应逻辑
}

通过while循环,即使发生虚假唤醒,线程也会重新进入等待状态,直到条件真正满足才继续执行。

四、总结

  • wait/notify:用于线程间的通信与协作,必须在同步环境中使用,且wait()要配合while循环防止虚假唤醒。
  • sleep:单纯的线程阻塞,不释放锁,适用于控制线程执行节奏。

理解这些方法的特性与使用规则,是编写正确、高效的多线程程序的基础。在实际开发中,合理运用这些工具,可以实现复杂的线程协作逻辑,同时避免因不当使用导致的各种问题。

你可能感兴趣的:(java,开发语言,spring,jvm)