核心区别速览表
| 特性 | wait() |
sleep() |
|---|---|---|
| 所属类 | Object 类的方法 |
Thread 类的静态方法 |
| 锁行为 | 释放当前对象的锁 | 不释放任何锁 |
| 唤醒方式 | 被其他线程调用 notify() 或 notifyAll() 唤醒 |
指定的时间到后自动唤醒 |
| 使用场景 | 线程间通信与协作,常用于生产者-消费者模型 | 让线程暂停执行一段固定时间,不涉及线程间通信 |
| 异常处理 | 必须在 synchronized 块或方法中调用,否则抛出 IllegalMonitorStateException |
可以在任何地方调用 |
| 本质 | 是对象监视器(锁)的一部分,用于线程等待 | 是线程的休眠,与锁无关 |
详细解析
wait()
wait() 是 Java 中实现线程间协作的核心机制,当一个线程调用某个对象的 wait() 方法时,它会:

- 释放锁:该线程会立即释放它对该对象的锁。
- 进入等待队列:该线程会进入该对象的等待队列,并暂停执行。
- 等待唤醒:它将一直等待,直到其他线程调用该对象的
notify()或notifyAll()方法来唤醒它。 - 重新获取锁:当它被唤醒后,它不会立即恢复执行,它会尝试重新获取该对象的锁,只有当成功获取锁后,它才能从
wait()调用的地方继续执行。
使用场景
wait() 主要用于一个线程需要等待另一个线程完成某个特定操作的场景,最经典的例子就是生产者-消费者模型。
- 消费者线程:当缓冲区为空时,消费者线程调用
buffer.wait(),让自己进入等待状态,并释放缓冲区的锁。 - 生产者线程:生产者向缓冲区中放入一个产品后,调用
buffer.notify(),唤醒可能在等待的消费者线程。
代码示例
public class WaitExample {
private static final Object lock = new Object();
public static void main(String[] args) {
Thread waitingThread = new Thread(() -> {
synchronized (lock) {
System.out.println("线程A:准备进入等待状态...");
try {
// 调用wait()会释放lock的锁
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程A:我被唤醒了,继续执行!");
}
});
waitingThread.start();
// 主线程等待一下,确保线程A已经进入wait状态
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 主线程作为另一个线程,去唤醒等待的线程
synchronized (lock) {
System.out.println("主线程:准备唤醒线程A...");
// 调用notify()会唤醒一个在lock对象上等待的线程
lock.notify();
}
}
}
执行流程:
- 线程A启动,获取
lock对象的锁。 - 线程A打印 "准备进入等待状态...",然后调用
lock.wait()。 - 调用
wait()后,线程A释放lock的锁,并进入等待状态。 - 主线程休眠1秒,确保线程A已经进入等待。
- 主线程尝试获取
lock对象的锁,由于线程A已经释放,所以主线程成功获取。 - 主线程打印 "准备唤醒线程A...",然后调用
lock.notify()。 notify()唤醒了在lock对象上等待的线程A,但此时线程A还不能立即执行,因为它还没有重新获取到lock的锁。- 主线程执行完
synchronized代码块,释放lock的锁。 - 线程A成功获取
lock的锁,从wait()方法处返回,继续执行,打印 "我被唤醒了,继续执行!"。 - 线程A执行完毕,释放
lock的锁。
sleep()
sleep() 是一个非常简单的方法,它让当前正在执行的线程暂停执行一段指定的时间。
- 不释放锁:调用
sleep()的线程在休眠期间,不会释放任何锁。 - 自动唤醒:休眠时间结束后,线程会自动从阻塞状态变为就绪状态,等待CPU调度。
- 静态方法:因为是静态方法,所以它只对当前正在执行的线程有效,即
Thread.sleep(1000)等同于当前线程.sleep(1000)。
使用场景
sleep() 主要用于以下场景:

- 模拟网络延迟或耗时操作。
- 在轮询(Polling)中,让CPU短暂休息,避免空转占用过多资源。
- 让程序暂停一段时间,方便调试。
代码示例
public class SleepExample {
private static final Object lock = new Object();
public static void main(String[] args) {
Thread sleepingThread = new Thread(() -> {
synchronized (lock) {
System.out.println("线程B:获取了锁,准备sleep 2秒...");
try {
// 调用sleep()不会释放lock的锁
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程B:sleep结束,释放锁。");
}
});
sleepingThread.start();
// 主线程尝试获取锁
try {
// 等待一下,确保线程B已经获取了锁并进入sleep
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock) {
System.out.println("主线程:尝试获取锁...");
// 这行代码会在线程B释放锁之前一直等待
System.out.println("主线程:成功获取了锁!");
}
}
}
执行流程:
- 线程B启动,获取
lock对象的锁。 - 线程B打印 "获取了锁,准备sleep 2秒...",然后调用
Thread.sleep(2000)。 - 线程B进入休眠状态,但没有释放
lock的锁。 - 主线程休眠500毫秒,然后尝试获取
lock的锁,由于线程B仍然持有锁,主线程被阻塞。 - 2秒后,线程B的休眠结束,它继续执行
synchronized代码块,打印 "sleep结束,释放锁。",然后释放lock的锁。 - 主线程此时才能获取到
lock的锁,打印 "成功获取了锁!"。
总结与类比
为了更好地理解,我们可以用一个生活中的例子来类比:
想象一个公共卫生间(对象锁):
-
wait():
(图片来源网络,侵删)- 你(线程A)走进卫生间,发现里面没纸了。
- 你不能一直占着卫生间等别人送纸,于是你走出卫生间,并对门口的服务员(对象)说:“卫生间没纸了,请送纸来后叫我一下”(
lock.wait())。 - 你(线程A)释放了对卫生间的占用(释放锁),在休息区等待(进入等待队列)。
- 服务员(另一个线程)送完纸后,在休息区找到你,说:“纸送到了”(
lock.notify())。 - 你(线程A)得到通知,然后再次去卫生间门口排队,等没人用的时候你再进去(重新获取锁)。
-
sleep():- 你(线程B)走进卫生间,准备上大号。
- 你发现手机快没电了,于是你决定充电5分钟(
Thread.sleep(5000))。 - 在这5分钟里,你虽然坐在马桶上没动,但你仍然占着卫生间(不释放锁)。
- 无论外面的人(主线程)多着急,他们都必须等你5分钟充电结束,你走出卫生间后才能进去。
最佳实践
-
永远不要在循环外使用
wait():因为wait()可能被意外唤醒(虚假唤醒),所以正确的做法是总是在一个while循环中检查条件。while (!condition) { object.wait(); } -
优先使用
java.util.concurrent包:在现代Java开发中,对于复杂的线程协作,应该优先使用BlockingQueue、CountDownLatch、Semaphore等高级并发工具,它们比直接使用wait()和notify()更安全、更易用、不易出错。 -
明确使用意图:如果你的线程需要等待某个条件成立,请使用
wait(),如果你的线程只是需要暂停一段时间,请使用sleep(),不要混用。
