在 Java 多线程编程中,wait()sleep() 是两个让新手极易混淆的方法,它们都能让当前线程暂停执行、进入等待状态,但本质、使用场景、锁机制、所属类完全不同。

本文将从核心定义、源码归属、锁行为、调用方式、唤醒机制、使用场景等维度,彻底讲清两者的区别,搭配代码示例帮你彻底吃透,告别面试和开发中的踩坑。

一、前置知识:必须先搞懂这两个基础概念

学习两者区别前,先明确两个关键前提,否则无法理解核心差异:

  1. 线程状态:Java 线程有 NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED 6 种状态,wait()sleep() 都会让线程进入等待类状态,但触发条件不同。
  2. 对象锁(Monitor):Java 中每个对象都有一个内置锁(监视器),synchronized 关键字就是通过抢占对象锁实现线程同步,锁是区分两者的核心

二、基础认知:wait () 和 sleep () 是什么?

1. wait () 方法

wait()java.lang.Object 类中的实例方法只能在同步代码块 / 同步方法中调用。作用:让当前持有对象锁的线程释放锁,并进入该对象的等待队列(WAITING 状态),直到被其他线程唤醒(notify()/notifyAll())或超时。

简单说:线程主动让出锁,等待被唤醒

2. sleep () 方法

sleep()java.lang.Thread 类中的静态方法可以在任何场景下调用。作用:让当前执行的线程暂停执行指定时间(毫秒 / 纳秒),进入 TIMED_WAITING 状态不会释放任何锁,时间到后自动恢复执行。

简单说:线程抱着锁睡觉,时间一到自己醒


三、核心区别:10 个维度彻底对比

这是面试高频考点,也是开发中必须掌握的核心,我整理了最全面的对比表:

对比维度 wait() sleep()
所属类 Object 类(所有对象都拥有) Thread 类(线程专属)
锁行为 会释放对象锁,释放后其他线程可抢占锁 不会释放任何锁,锁仍被当前线程持有
调用限制 必须在 synchronized 同步代码中调用 无限制,任何地方都能直接调用
线程状态 进入 WAITING(无限等待)/TIMED_WAITING 固定进入 TIMED_WAITING(计时等待)
唤醒机制 必须被 notify()/notifyAll() 主动唤醒 时间到期自动唤醒,无需外部干预
使用场景 线程间通信 / 协作(生产者 - 消费者模型) 线程暂停执行(延时、定时操作)
方法属性 实例方法(依赖具体对象调用) 静态方法(属于 Thread 类,与对象无关)
异常处理 必须捕获 / 抛出 InterruptedException 必须捕获 / 抛出 InterruptedException
归属逻辑 属于锁等待,是线程间同步机制 属于线程调度,是线程自身暂停机制
底层原理 基于对象监视器(Monitor)实现 基于操作系统线程调度实现

四、核心区别深度解析(重点!)

1. 所属类不同:最基础的区别

  • wait()Object 的方法:意味着Java 中所有对象都能调用 wait (),因为所有类都继承自 Object。
  • sleep()Thread 的静态方法:只属于线程,和具体对象无关。

为什么 wait () 要放在 Object 类中?因为 wait() 操作的是对象锁,锁是绑定在每个对象上的,而非线程。只有持有对象锁的线程才能调用该对象的 wait(),所以必须定义在 Object 类中。

2. 锁行为:最核心、最关键的区别

这是两者本质区别,也是开发中最容易出错的点:

  • wait () 会释放锁:调用后,当前线程立即释放持有的对象锁,其他线程可以进入同步代码块执行。
  • sleep () 不释放锁:调用后,线程暂停,但死死抱着锁不放,其他线程无法进入同步区域。

3. 调用条件:必须满足才能用

  • wait()强制要求在 synchronized 修饰的同步方法 / 代码块中调用,否则运行时抛出 IllegalMonitorStateException 异常。
  • sleep():无任何限制,主线程、子线程、同步代码内外都能直接调用。

4. 唤醒方式不同

  • wait()
    1. 无参 wait():无限等待,必须被其他线程调用 notify()/notifyAll() 唤醒;
    2. 有参 wait(long timeout):计时等待,时间到或被唤醒都会恢复。
  • sleep(long millis):计时结束自动恢复,无需任何外部唤醒。

五、代码示例:直观感受区别

示例 1:wait () 释放锁的特性

我们创建两个线程,测试 wait() 调用后是否释放锁:

public class WaitDemo {
    // 共享锁对象
    private static final Object lock = new Object();

    public static void main(String[] args) {
        // 线程1:调用wait()
        new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程1获取锁,开始执行");
                try {
                    // 调用wait(),释放lock锁
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程1被唤醒,继续执行");
            }
        }, "线程1").start();

        // 线程2:等待线程1释放锁后执行
        new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程2获取锁,开始执行");
                // 唤醒等待lock的线程
                lock.notify();
            }
        }, "线程2").start();
    }
}

执行结果

线程1获取锁,开始执行
线程2获取锁,开始执行
线程1被唤醒,继续执行

✅ 结论:线程 1 调用 wait()释放了锁,线程 2 才能成功抢占锁执行。


示例 2:sleep () 不释放锁的特性

同样的代码,把 wait() 换成 sleep()

public class SleepDemo {
    private static final Object lock = new Object();

    public static void main(String[] args) {
        // 线程1:调用sleep()
        new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程1获取锁,开始执行");
                try {
                    // 睡眠3秒,不释放锁
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程1睡眠结束,执行完毕");
            }
        }, "线程1").start();

        // 线程2:必须等线程1释放锁才能执行
        new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程2获取锁,开始执行");
            }
        }, "线程2").start();
    }
}

执行结果

线程1获取锁,开始执行
// 等待3秒后才输出下一行
线程1睡眠结束,执行完毕
线程2获取锁,开始执行

✅ 结论:线程 1 调用 sleep()没有释放锁,线程 2 必须等待 3 秒后线程 1 执行完毕才会执行。


六、使用场景:什么时候用哪个?

1. wait () 使用场景

用于线程间通信、协作,是多线程同步的核心:

  • 经典的生产者 - 消费者模型(生产者生产数据后 wait (),消费者消费后 notify ());
  • 线程间等待 / 唤醒逻辑(比如一个线程等待另一个线程的执行结果)。

2. sleep () 使用场景

用于线程自身暂停、延时执行,和线程通信无关:

  • 定时任务延时执行;
  • 模拟网络请求、IO 操作的延时;
  • 控制线程执行频率。

七、常见面试题 & 避坑指南

1. 高频面试题

  1. wait () 和 sleep () 都会释放锁吗?答:wait() 会释放锁,sleep() 不会释放锁。
  2. 为什么 wait () 要放在 Object 类,而 sleep () 放在 Thread 类?答:wait() 操作对象锁,锁属于对象;sleep() 是线程自身行为,属于线程。
  3. wait () 可以被中断吗?答:可以,两者都会抛出 InterruptedException,支持线程中断。

2. 避坑指南

  1. 调用 wait()/notify() 必须在 synchronized 同步代码中,否则抛异常;
  2. wait() 建议写在 while 循环中(防止虚假唤醒),不要用 if
  3. 优先使用 notifyAll() 而非 notify(),避免线程饥饿;
  4. sleep() 是静态方法,不要用线程对象调用(thread.sleep()),语义错误。

八、总结

用一句话记住核心区别:wait () 是让持有锁的线程释放锁并等待,属于线程间协作;sleep () 是让线程抱着锁睡觉,属于线程自身延时。

核心点 wait() sleep()
释放 不释放
所属 Object Thread
调用 同步代码内 任意位置
唤醒 需 notify () 自动唤醒
场景 线程通信 线程延时

总结

  1. 核心差异wait() 释放对象锁、用于线程通信;sleep() 不释放锁、用于线程延时;
  2. 使用限制wait() 必须在同步代码中调用,sleep() 无限制;
  3. 唤醒方式wait() 需主动唤醒,sleep() 时间到自动醒;
  4. 开发原则:线程协作用 wait()/notify(),单纯延时用 sleep()
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐