深入理解 sleep() 与 wait():从基础到监视器队列

前言

看似都是"让线程停下来",背后的原理却完全不同

在 Java 并发编程中,sleep()wait() 是两个经常被拿来比较的方法。很多初学者甚至有一定经验的开发者,也容易混淆它们。今天这篇文章,我们就从基础区别 一路深入到监视器锁的队列机制,彻底搞懂这两个方法。


一、五分钟快速掌握核心区别

先上结论,后面再展开细节。

对比维度 sleep() wait()
所属类 Thread 类的静态方法 Object 类的实例方法
调用前提 任意地方均可调用 必须在同步块/同步方法中
是否释放锁 ❌ 不会释放任何锁 ✅ 释放当前对象的锁
唤醒方式 时间到自动唤醒 需要 notify / notifyAll 唤醒(也可超时唤醒)
典型场景 暂停执行、模拟耗时 线程间通信、等待条件满足

二、从代码看差异

2.1 sleep() 用法

java 复制代码
public class SleepDemo {
    public static void main(String[] args) {
        System.out.println("开始睡眠...");
        try {
            Thread.sleep(3000);  // 暂停 3 秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("醒了!");
    }
}

sleep() 可以在任何地方调用,它让当前线程 暂停指定时间,但不会释放它持有的任何锁

2.2 wait() 用法

java 复制代码
public class WaitDemo {
    private static final Object lock = new Object();

    public static void main(String[] args) {
        synchronized (lock) {        // 必须要有 synchronized
            try {
                System.out.println("进入等待...");
                lock.wait();         // 释放 lock 锁,进入等待
                System.out.println("被唤醒,继续执行");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

如果去掉 synchronized 块,运行时会直接抛出:

复制代码
Exception in thread "main" java.lang.IllegalMonitorStateException

三、深入监视器:wait() 后的线程去哪儿了?

这是面试中经常追问的点。要回答清楚,需要理解 Java 对象监视器(Monitor)的内部结构。

3.1 每个对象都有两个"队列"

对于一个被 synchronized 修饰的对象,JVM 会为它维护两个队列:

队列名称 英文 存放的线程 线程状态
竞争队列 Entry Set / Contention List 想获取锁但还没抢到的线程 BLOCKED
等待队列 Wait Set 已经持有锁、但调用 wait() 主动放弃的线程 WAITING

3.2 队列流转图

复制代码
                    ┌─────────────────────────────────────┐
                    │           竞争队列 (Entry Set)       │
                    │    Thread-B (BLOCKED)                │
                    │    Thread-C (BLOCKED)                │
                    └─────────────────┬───────────────────┘
                                      │
                     锁释放后,JVM 从竞争队列选一个成为 Owner
                                      ▼
                    ┌─────────────────────────────────────┐
                    │         Monitor Owner               │
                    │      (当前持有锁的线程)               │
                    └─────────────────┬───────────────────┘
                                      │
                        owner 调用 wait() 后释放锁
                                      │
                                      ▼
                    ┌─────────────────────────────────────┐
                    │           等待队列 (Wait Set)        │
                    │    Thread-A (WAITING)                │
                    │    Thread-D (WAITING)                │
                    └─────────────────────────────────────┘

3.3 关键流转规则

  1. 竞争队列 → Owner:Owner 释放锁后,JVM 从竞争队列中选一个线程获得锁
  2. Owner → 等待队列 :Owner 调用 wait() → 释放锁 → 进入等待队列,状态变为 WAITING
  3. 等待队列 → 竞争队列 :被 notify / notifyAll 唤醒 → 移入竞争队列,状态变为 BLOCKED → 重新参与锁竞争

💡 注意:被 notify 唤醒后,线程并不会立即执行,它只是从"等待队列"进入了"竞争队列",需要重新竞争锁。竞争成功后才能从 wait() 方法返回。


四、wait/notify 实现线程间通信

这是生产者-消费者模式中最经典的协作方式。

4.1 完整示例

java 复制代码
public class WaitNotifyCommunication {

    private static final Object lock = new Object();
    private static boolean condition = false;   // 共享条件

    // 等待线程
    static class Waiter extends Thread {
        @Override
        public void run() {
            synchronized (lock) {
                while (!condition) {                    // ⚠️ 必须用 while,防止虚假唤醒
                    try {
                        System.out.println(Thread.currentThread().getName() + ":条件不满足,进入等待队列");
                        lock.wait();                    // 释放锁,进入 WAITING
                        System.out.println(Thread.currentThread().getName() + ":被唤醒,重新获得锁");
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }
                System.out.println(Thread.currentThread().getName() + ":条件满足,继续执行任务");
            }
        }
    }

    // 通知线程
    static class Notifier extends Thread {
        @Override
        public void run() {
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName() + ":修改条件");
                condition = true;
                System.out.println(Thread.currentThread().getName() + ":通知等待队列中的线程");
                lock.notify();      // 将等待队列中的一个线程移到竞争队列
                // 注意:此时被唤醒的线程还在竞争队列,并未立即执行
            }
            // 退出同步块后释放锁,被唤醒的线程才能竞争到锁并继续执行
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Waiter waiter = new Waiter();
        waiter.setName("Waiter");
        Notifier notifier = new Notifier();
        notifier.setName("Notifier");

        waiter.start();
        Thread.sleep(100);  // 确保 waiter 先拿到锁并进入等待
        notifier.start();
    }
}

执行结果

复制代码
Waiter:条件不满足,进入等待队列
Notifier:修改条件
Notifier:通知等待队列中的线程
Waiter:被唤醒,重新获得锁
Waiter:条件满足,继续执行任务

4.2 为什么必须用 while 而不是 if?

java 复制代码
// ❌ 错误写法
if (!condition) {
    wait();
}

// ✅ 正确写法
while (!condition) {
    wait();
}

原因 :线程被唤醒后,条件可能再次不成立 (比如被另一个线程抢先修改了)。用 while 循环可以重新检查条件,这就是所谓的防范虚假唤醒


五、一张表总结

对比项 sleep() wait()
定义位置 Thread 静态方法 Object 实例方法
所属 线程级别 对象级别
同步要求 必须在 synchronized
锁释放 不释放 释放当前对象锁
唤醒 自动唤醒 需要 notify / notifyAll
状态变化 RUNNABLETIMED_WAITING RUNNABLEWAITING / TIMED_WAITING
典型用途 暂停线程、模拟耗时 等待条件、线程协作

六、思考题

如果线程 A 持有锁 lock1,然后调用 lock1.wait(),它会释放 lock1 的锁。但如果它同时还持有另一把锁 lock2,lock2 会被释放吗?

答案 :不会。wait() 只释放当前对象(lock1)的锁,其他锁不受影响。这也是为什么 wait/notify 必须成对使用同一个对象的原因。


写在最后

sleep()wait() 看似都是让线程暂停,但本质上一个作用于线程本身,一个作用于对象监视器。理解了这两个方法的区别以及监视器的队列机制,Java 并发编程的很多问题都会变得清晰起来。

希望这篇文章对你有帮助。如果觉得不错,欢迎点赞、收藏、转发


相关推荐
其实防守也摸鱼1 分钟前
智能体推荐:精选 AI Agent 工具与实战指南
运维·开发语言·人工智能·学习·web安全·自动化
SimonKing1 分钟前
开源神器 Navop:数据库+SSH+终端+AI,一个应用全搞定
java·后端·程序员
always_TT1 分钟前
【Python 字符串格式化:format() 方法】
android·开发语言·python
爱码猿4 分钟前
SpringBoot+MybatisPlus动态数据源
java·spring boot·后端
小义_5 分钟前
JDK 深度解析
java·linux·开发语言·python·面试
智购科技自动售货机工厂20 分钟前
2026自动售货机异常重启根因分析:从日志挖掘到内存取证的技术实践~YH
开发语言·数据库·单片机·嵌入式硬件·人机交互
奈斯先生Vector26 分钟前
从“能调用”到“可运营”:AI Agent 进入多模型时代后的架构升级
java·javascript·数据库·人工智能·算法·架构·aigc
Escalating_xu26 分钟前
【C++类和对象(上)】从类的定义、封装与对象模型到内存对齐和 this 指针
开发语言·前端·c++
Json____29 分钟前
宿舍安全卫生检查系统:Node 全栈开发实战
java·前端·数据库·毕业设计·课程设计·毕设·wwwoop.com
阿里嘎多学长31 分钟前
2026-08-30 GitHub 热点项目精选
开发语言·程序员·github·代码托管