这是一个非常经典且重要的面试题,理解它们的差异是掌握 Java 多线程编程的关键。

一句话核心区别
sleep() 是 线程 的方法,它让当前线程暂停执行,但不释放锁。
wait() 是 对象 的方法,它让当前线程等待,并立即释放锁。
为了更清晰地理解,我们从多个维度进行详细对比。
详细对比表格
| 特性 | sleep() |
wait() |
|---|---|---|
| 所属类 | java.lang.Thread |
java.lang.Object |
| 调用者 | 在当前线程上直接调用 | 在同步代码块或同步方法中,通过锁对象(this 或其他对象)调用 |
| 锁的释放 | 不释放任何锁,线程在睡眠期间,仍然持有它获取的所有锁。 | 立即释放锁,调用 wait() 的线程会释放其持有的锁,以便其他线程可以获取该锁。 |
| 唤醒方式 | 指定时间到期后自动唤醒。 被其他线程中断 ( interrupt())。 |
其他线程调用 notify() 或 notifyAll()。指定等待时间到期(如果调用了 wait(long timeout))。被其他线程中断。 |
| 使用场景 | 主要用于暂停当前线程的执行,通常用于简单的定时、延时或轮询场景。 | 主要用于线程间的通信与协作,当一个线程需要等待某个条件(如资源可用)时,它调用 wait() 进入等待状态,直到其他线程满足条件后调用 notify() 唤醒它。 |
| 是否需要同步 | 不需要,可以在任何地方调用。 | 必须在同步上下文(synchronized 代码块或方法)中调用,否则会抛出 IllegalMonitorStateException。 |
| 异常处理 | 抛出 InterruptedException。 |
抛出 InterruptedException。 |
深入解析与代码示例
sleep() - 线程的“暂停键”
sleep() 就像一个线程的“暂停键”,它告诉线程:“嘿,你先别运行了,睡一会儿,时间到了我再继续。”
特点:

- 无锁关系:
sleep()和锁没有任何关系,一个线程在sleep时,它依然牢牢地抓着它获取的锁,其他想访问这个同步资源的线程只能干等着。
示例代码:
class SleepDemo {
private static final Object lock = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (lock) {
System.out.println("线程 A: 获取到锁,准备执行 3 秒...");
try {
// 线程 A 睡觉,但依然持有锁
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程 A: 醒来了,继续执行。");
}
}, "线程 A").start();
new Thread(() -> {
synchronized (lock) {
System.out.println("线程 B: 必须等待线程 A 释放锁才能进入。");
}
}, "线程 B").start();
}
}
输出结果分析:
你会看到 "线程 A: 获取到锁..." 打印后,立即会等待 3 秒,然后才会打印 "线程 A: 醒来了...",在这 3 秒内,"线程 B" 的打印语句绝对不会出现,因为即使线程 A 在 sleep,它也持有 lock 对象的锁,线程 B 无法进入 synchronized 代码块。
wait() - 线程的“等待协作”
wait() 是多线程协作的基石,它就像一个线程说:“我现在需要的条件不满足,我先不干了,我把锁让出来,谁满足了条件记得叫我一声(notify)。”
特点:

- 与锁强绑定:
wait()必须在synchronized代码块中调用,因为它操作的是“锁”的状态,调用wait()的线程会释放锁,并进入该对象的等待队列。 - 需要被唤醒:
wait()不会自己醒来(除非设置了超时),必须由其他线程调用同一个对象的notify()或notifyAll()来唤醒。
示例代码:
class WaitNotifyDemo {
private static final Object lock = new Object();
private static boolean condition = false; // 共享条件
public static void main(String[] args) {
// 等待线程
new Thread(() -> {
synchronized (lock) {
System.out.println("等待线程: 检查条件,条件不满足,开始等待...");
while (!condition) { // 使用 while 循环防止虚假唤醒
try {
// 释放锁,进入等待状态
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("等待线程: 条件已满足,被唤醒,继续执行!");
}
}, "等待线程").start();
// 通知线程
try {
Thread.sleep(1000); // 让等待线程先执行并进入等待状态
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
synchronized (lock) {
System.out.println("通知线程: 准备满足条件...");
condition = true; // 修改共享状态
System.out.println("通知线程: 条件已满足,唤醒等待线程。");
// 唤醒在 lock 对象上等待的一个线程
lock.notify();
}
}, "通知线程").start();
}
}
输出结果分析:
- "等待线程" 启动,获取锁,发现
condition为false,调用lock.wait()。 - 调用
wait()后,"等待线程"立即释放锁,并进入lock对象的等待队列,阻塞。 - "通知线程" 启动,由于 "等待线程" 已经释放了锁,"通知线程" 可以顺利获取
lock。 - "通知线程" 修改
condition = true,然后调用lock.notify()。 notify()会从lock的等待队列中随机挑选一个线程(这里是"等待线程")唤醒,并将其移入锁的同步队列。- "通知线程" 释放锁后,"等待线程" 从同步队列中获取锁,从
wait()的地方恢复执行。 - "等待线程" 再次检查
while (!condition)循环,condition为true,循环退出,继续执行后续代码。
总结与最佳实践
-
功能不同:
sleep()用于控制时间,让线程暂时休眠。wait()用于控制流程,实现线程间的协作通信。
-
锁处理是核心:
sleep()不释放锁,wait()释放锁,这是两者最根本、最重要的区别。
-
使用
wait()时的最佳实践:- 永远在
while循环中检查条件:while (!condition) { lock.wait(); },因为wait()可能被意外地“虚假唤醒”(Spurious Wakeup),即在没有被notify()的情况下线程也可能苏醒,使用while循环可以确保在苏醒后再次检查条件是否真的满足。 - 必须在
synchronized代码块中调用:这是为了保证线程对共享状态的互斥访问。
- 永远在
-
如何选择?
- 如果你只是想让当前线程停顿一段时间(比如定时任务、延时),使用
Thread.sleep()。 - 如果你需要根据某个共享状态来协调多个线程的执行顺序(比如生产者-消费者模型),使用
Object.wait()和Object.notify()/notifyAll()。
- 如果你只是想让当前线程停顿一段时间(比如定时任务、延时),使用
一个生动的比喻
想象一个办公室(共享资源)里有一间会议室(锁)。
-
sleep():小王(线程 A)在开会,他突然有点困,于是说:“我先睡 10 分钟,别打扰我。” 他关着门睡觉,会议室还是被他占着,小李(线程 B)想来开会,只能在外面等着。 -
wait():小王在开会,但他发现会议资料(条件)还没到,他无法继续,于是他对同事说:“我先出去等一下,资料到了麻烦叫我一声。” 说完,他打开门离开了会议室(释放锁),去茶水间(等待队列)等待,这时,小李就可以进入会议室开会了,过了一会儿,资料送到了,同事(通知线程)去茶水间找到小王,告诉他:“资料到了!”(notify()),小王回到会议室门口排队(同步队列),等小李开完会出来(释放锁),小王再进去继续开会。
希望这个详细的解释和比喻能帮助你彻底理解 sleep() 和 wait() 的区别!
