wait() 和 sleep() 的区别?

2025年 阅读约 8 分钟 面试指南 · Java面试 · Java并发

深入对比wait()和sleep():锁释放机制、所属类(Object vs Thread)、唤醒方式(notify vs 超时)、使用场景(线程通信 vs 暂停执行)、线程状态变化,附完整源码分析和面试模拟。

一句话总结

核心区别:wait() 释放锁,sleep() 不释放锁。wait() 是 Object 的方法,必须在 synchronized 中调用,用于线程间通信,通过 notify() 唤醒;sleep() 是 Thread 的静态方法,可在任意位置调用,用于暂停执行,超时自动恢复。wait() 使线程进入 WAITING 状态,sleep() 使线程进入 TIMED_WAITING 状态。

初级理解

核心区别一览

用途
特性wait()sleep()
所属类java.lang.Objectjava.lang.Thread
释放锁✅ 释放❌ 不释放
调用位置必须在 synchronized 中任意位置
唤醒方式notify()/notifyAll()超时自动恢复 / interrupt()
线程状态WAITING / TIMED_WAITINGTIMED_WAITING
线程间通信暂停执行
静态方法否(实例方法)是(静态方法)

锁释放对比

// wait() — 释放锁 synchronized (obj) { System.out.println("持有锁"); obj.wait(); // 释放锁,进入 WAITING // 被唤醒后重新获取锁,继续执行 System.out.println("重新获取锁"); } // sleep() — 不释放锁 synchronized (obj) { System.out.println("持有锁"); Thread.sleep(1000); // 仍然持有锁!其他线程无法进入 System.out.println("仍然持有锁"); }
一句话总结:wait 释放锁用于通信,sleep 不释放锁用于暂停。这是最核心的区别。

中级深入

wait/notify 的经典用法

// 生产者-消费者模式 public class ProducerConsumer { private final Queue<Integer> queue = new LinkedList<>(); private final int capacity = 10; public synchronized void produce(int value) throws InterruptedException { while (queue.size() == capacity) { // 用 while 而非 if! wait(); // 队列满,等待消费者消费 } queue.add(value); notifyAll(); // 唤醒消费者 } public synchronized int consume() throws InterruptedException { while (queue.isEmpty()) { // 用 while 而非 if! wait(); // 队列空,等待生产者生产 } int value = queue.remove(); notifyAll(); // 唤醒生产者 return value; } }

为什么 wait 必须在 synchronized 中?

wait 的语义是"释放锁并等待"。如果不在 synchronized 中,就没有锁可以释放,会抛出 IllegalMonitorStateException。此外,wait/notify 的通信机制依赖于锁对象的监视器(monitor),只有持有锁的线程才能操作监视器。

为什么用 while 而不是 if?

// 错误:用 if 判断条件 synchronized (obj) { if (queue.isEmpty()) { obj.wait(); // 被唤醒后直接往下执行 } queue.remove(); // 可能队列还是空的!(虚假唤醒) } // 正确:用 while 判断条件 synchronized (obj) { while (queue.isEmpty()) { obj.wait(); // 被唤醒后重新检查条件 } queue.remove(); // 确保队列非空 } // 原因: // 1. 虚假唤醒(spurious wakeup):线程可能在没有 notify 的情况下被唤醒 // 2. 多消费者场景:notifyAll 唤醒所有线程,但只有一个能拿到数据

notify vs notifyAll

方法行为适用场景
notify()随机唤醒一个等待线程所有等待线程条件相同
notifyAll()唤醒所有等待线程等待线程条件不同(推荐)
中级要点:wait 必须在 synchronized 中,用 while 检查条件防止虚假唤醒,优先用 notifyAll 避免信号丢失。

高级拓展

wait 的底层原理

wait() 底层调用 ObjectMonitor 的 wait 方法:将当前线程加入 WaitSet,释放锁(退出 monitor),然后通过 park 阻塞自己。被 notify 后,线程从 WaitSet 移到 EntryList,重新竞争锁。

// wait() 的底层流程(简化) // 1. 将当前线程包装成 ObjectWaiter 节点 // 2. 加入 WaitSet 队列 // 3. 释放锁(退出 monitor) // 4. park 阻塞当前线程 // notify() 的底层流程(简化) // 1. 从 WaitSet 取出一个节点 // 2. 将该节点移到 EntryList // 3. 该线程在 EntryList 中竞争锁

sleep(0) 的作用

Thread.sleep(0) 不会让线程进入 TIMED_WAITING,而是触发一次 CPU 调度,让操作系统有机会切换到其他同优先级线程。这是一种"谦让"机制。

LockSupport.park/unpark vs wait/notify

特性wait/notifypark/unpark
所属类ObjectLockSupport
需要锁必须在 synchronized 中不需要
顺序敏感必须先 wait 再 notify可以先 unpark 再 park(许可累加)
唤醒精确性notify 随机唤醒unpark 精确唤醒指定线程
高级加分项:能解释 wait 的底层 ObjectMonitor 流程,知道 sleep(0) 的调度作用,能对比 park/unpark 和 wait/notify 的优劣。

实战场景

场景一:交替打印奇偶数

public class OddEvenPrinter { private int num = 1; private final int max = 100; public synchronized void printOdd() throws InterruptedException { while (num <= max) { while (num % 2 == 0) wait(); // 偶数则等待 System.out.println("奇数: " + num++); notify(); // 唤醒偶数线程 } } public synchronized void printEven() throws InterruptedException { while (num <= max) { while (num % 2 == 1) wait(); // 奇数则等待 System.out.println("偶数: " + num++); notify(); // 唤醒奇数线程 } } }

常见坑

原因解决方案
wait 不在 synchronized 中没有锁可释放确保在同步块中调用
用 if 判断条件虚假唤醒导致条件不满足用 while 循环检查
notify 后忘记释放锁notify 不释放锁,同步块结束才释放理解 notify 后线程仍在同步块中
sleep 在 synchronized 中持有锁睡觉,其他线程干等用 wait 代替,或缩小同步范围

面试模拟

Q:wait() 和 sleep() 的区别?

A:1) 锁释放:wait 释放锁,sleep 不释放;2) 所属类:wait 是 Object 方法,sleep 是 Thread 静态方法;3) 调用位置:wait 必须在 synchronized 中,sleep 任意位置;4) 唤醒:wait 需要 notify,sleep 超时自动恢复;5) 用途:wait 用于线程通信,sleep 用于暂停执行。

Q:为什么 wait 必须在 synchronized 中?

A:wait 的语义是"释放锁并等待",没有锁就无法释放。底层通过 ObjectMonitor 实现,只有持有锁的线程才能操作监视器的 WaitSet。不在 synchronized 中调用会抛 IllegalMonitorStateException。

Q:为什么 wait 的条件判断要用 while 而不是 if?

A:两个原因:1) 虚假唤醒:线程可能在没有 notify 的情况下被操作系统唤醒;2) notifyAll 竞争:多个线程被唤醒后只有一个能拿到资源,其他线程需要重新检查条件。用 while 确保被唤醒后条件仍然满足。