sleep和wait区别?
本文对比
sleep和wait。核心区别:sleep是线程暂停,不释放锁;wait是线程等待,会释放锁,用于线程间通信。
这是一个非常经典且重要的 Java 多线程面试题。sleep() 和 wait() 的区别是理解 Java 线程同步和通信的关键。
我会从多个维度来详细解释它们的区别,并提供一个生动的比喻和代码示例来帮助你彻底理解。
一、核心区别(一句话总结)
sleep()是让线程“睡一会”,时间到了自己会醒,但不会释放它持有的锁。wait()是让线程进入“等待状态”,它会释放持有的锁,并且需要其他线程通过notify()或notifyAll()来唤醒。
二、详细对比表格
| 特性 | Thread.sleep() |
Object.wait() |
|---|---|---|
| 归属类 | java.lang.Thread |
java.lang.Object |
| 锁的释放 | 不释放锁 | 释放锁 (这是最本质的区别) |
| 使用前提 | 可以在任何地方使用 | 必须在同步代码块或同步方法中 (synchronized) 使用 |
| 唤醒条件 | 1. 睡眠时间到 2. 被 interrupt() 中断 |
1. 其他线程调用该对象的 notify() 或 notifyAll() 2. wait(long timeout) 时间到 3. 被 interrupt() 中断 |
| 方法类型 | static 静态方法,控制的是当前线程 |
final 实例方法,控制的是调用该方法的对象上的等待线程 |
| 主要用途 | 暂停线程,让出 CPU 时间片,但不涉及线程间协作 | 线程间的通信与协作 |
三、深入解析
1. 归属类不同
sleep(long millis)是Thread类的静态方法。这意味着它直接作用于当前正在执行的线程。你调用Thread.sleep(1000),就是让当前线程休眠1秒。即使你用一个线程实例去调用t1.sleep(1000),休眠的仍然是当前执行这段代码的线程,而不是t1线程。wait()是Object类的方法。这意味着任何 Java 对象都可以调用它。它作用于调用wait()方法的那个对象,让当前线程在该对象上等待。
2. 对锁的处理(最重要的区别)
这是面试官最想听到的点。
sleep()不会释放锁:如果一个线程在synchronized代码块中调用了sleep(),它会暂停执行,但它仍然持有这个锁。其他线程无法进入这个synchronized代码块,必须等待sleep()的线程醒来并执行完毕后释放锁。wait()会释放锁:如果一个线程在synchronized代码块中调用了某个对象的wait()方法,它会立即释放该对象的锁,并进入该对象的等待队列(Waiting Queue)。这样,其他线程就有机会获得这个锁并进入synchronized代码块。
3. 使用前提不同
sleep()可以在任何地方调用。wait()必须在synchronized方法或synchronized代码块中调用。否则,程序会在运行时抛出IllegalMonitorStateException异常。这是因为wait()的操作是基于锁的,你必须先持有这个锁,才能在它上面等待和释放。
4. 唤醒方式不同
sleep()的线程会在指定的时间后自动唤醒,或者被其他线程调用interrupt()方法中断时唤醒。wait()的线程需要依赖其他线程。当另一个线程获得了同一个对象的锁后,调用该对象的notify()(随机唤醒一个等待的线程)或notifyAll()(唤醒所有等待的- 线程)时,等待的线程才会被唤醒,并重新尝试获取锁。
四、场景比喻
想象一个公共卫生间,只有一个坑位,门上有一把锁(Lock)。
sleep()的场景:- 张三进了卫生间,把门锁上了 (
synchronized)。 - 他突然觉得有点困,就坐在马桶上睡了10分钟 (
sleep(600000))。 - 在这10分钟里,他一直占着坑位,并且门是锁着的(不释放锁)。
- 外面的李四内急,想进去,但门被锁着,只能在外面干等着,直到张三睡醒、办完事、出来、把门打开(释放锁)。
- 总结:
sleep是自己暂停,但不影响自己对资源的占用。
- 张三进了卫生间,把门锁上了 (
wait()/notify()的场景:- 张三进了卫生间,把门锁上了 (
synchronized)。 - 他发现没纸了,他不能继续了。于是他打开门,对外面喊:“谁给我送点纸过来?” (
wait())。喊完后,他就走出了坑位,把门也解锁了(释放锁),然后在门口等着。 - 这时,李四可以进去了。他进去后,把门锁上 (
synchronized)。 - 李四办完事,看到旁边有一卷新纸,他把纸放在了纸架上,然后冲着门外的张三喊:“纸来了!” (
notify())。 - 李四办完事出来,解锁开门(释放锁)。
- 张三听到后,重新进去,锁上门(再次获取锁),拿到纸,继续他的事情。
- 总结:
wait是因为条件不满足而主动让出资源,等待别人满足条件后通知自己。这是典型的线程间协作。
- 张三进了卫生间,把门锁上了 (
五、代码示例
这个例子清晰地展示了 sleep 和 wait 对锁的不同处理。
java
public class SleepVsWait {
private static final Object LOCK = new Object();
public static void main(String[] args) {
// 演示 sleep
new Thread(() -> {
System.out.println("Thread A 准备获取锁...");
synchronized (LOCK) {
System.out.println("Thread A 获取到锁");
try {
System.out.println("Thread A sleep 2秒, 但不释放锁");
Thread.sleep(2000); // 持有锁,睡2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread A 睡醒了,执行完毕,释放锁");
}
}, "Thread A").start();
// 给A一点时间先启动
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 演示 wait
new Thread(() -> {
System.out.println("Thread B 准备获取锁...");
synchronized (LOCK) {
System.out.println("Thread B 获取到锁");
try {
System.out.println("Thread B wait, 释放锁,等待被唤醒");
LOCK.wait(); // 释放锁,并在此等待
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread B 被唤醒了,重新获取锁并执行完毕");
}
}, "Thread B").start();
// 演示 notify
new Thread(() -> {
System.out.println("Thread C 准备获取锁...");
synchronized (LOCK) {
System.out.println("Thread C 获取到锁");
System.out.println("Thread C 调用 notify() 唤醒一个等待的线程");
LOCK.notify(); // 唤醒在LOCK上等待的线程B
System.out.println("Thread C 执行完毕,释放锁");
// 注意:notify()后不会立即释放锁,要等同步代码块执行完
}
}, "Thread C").start();
}
}
代码执行顺序分析 (一个可能的输出):
Thread A启动,获取LOCK锁,打印信息,然后sleep(2000)。此时它持有锁。Thread B启动,尝试获取LOCK锁,但因为A持有锁,B被阻塞,无法进入同步块。Thread C启动,尝试获取LOCK锁,同样被阻塞。- 2秒后,
Thread A睡醒,执行完同步块,释放锁。 Thread B和Thread C竞争锁。假设Thread B抢到了。Thread B进入同步块,打印信息,然后调用LOCK.wait()。它释放锁并进入等待状态。Thread C现在可以获取LOCK锁了,因为它被B释放了。Thread C进入同步块,打印信息,调用LOCK.notify()。这个通知会发给正在等待的B。Thread C执行完同步块,释放锁。Thread B被唤醒,它会重新尝试获取锁。一旦获取成功,它会从wait()的地方继续执行,打印信息,然后执行完毕。
这个例子完美地展示了 sleep 占着锁睡觉,而 wait 释放锁让别人先干的本质区别。