核心区别速览表
| 特性 | sleep() |
wait() |
|---|---|---|
| 所属类 | java.lang.Thread 类 |
java.lang.Object 类 |
| 锁的释放 | 不释放 | 释放 |
| 唤醒方式 | 自动唤醒(时间到) | 被动唤醒(需要 notify() 或 notifyAll()) |
| 使用场景 | 暂停当前线程,不涉及线程间通信 | 线程间通信,让线程等待某个条件满足 |
| 调用前提 | 可以在任何地方调用 | 必须在同步代码块或同步方法中调用 |
| 异常处理 | 不抛出 InterruptedException(但方法会检查中断状态并返回) |
抛出 InterruptedException |
| 参数 | 指定毫秒数或纳秒数 | 可以不带参数(永久等待)或带超时时间 |
详细解释与代码示例
sleep() - 线程的“小憩”
sleep() 是 Thread 类的一个静态方法,它就像让你“小憩”一会儿,设定一个闹钟,时间到了自然就醒。

核心特点:
- 不释放锁:当一个线程调用
sleep()进入休眠时,它仍然持有它所获得的锁,这意味着其他需要这个锁的线程必须继续等待,即使调用sleep()的线程已经“睡着了”。 - 自动唤醒:
sleep()方法会指定一个休眠时间,当时间到达后,JVM 会将线程从“阻塞”(Blocked)状态变回“就绪”(Runnable)状态,等待 CPU 调度。
代码示例:
public class SleepExample {
public static void main(String[] args) {
Runnable task = () -> {
synchronized (SleepExample.class) { // 获取锁
System.out.println(Thread.currentThread().getName() + " 获取到锁,准备睡觉...");
try {
Thread.sleep(3000); // 睡 3 秒,期间不释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 醒来了,继续执行。");
}
};
new Thread(task, "线程A").start();
new Thread(task, "线程B").start();
}
}
执行结果分析:
- 线程A 和 线程B 都会竞争
SleepExample.class这个锁。 - 假设线程A 先获取到锁,它会打印“准备睡觉...”,然后调用
sleep(3000)。 - 关键点:尽管线程A 睡着了,但它仍然持有锁,线程B 无法进入
synchronized代码块,只能在外面等待。 - 3秒后,线程A 醒来,打印“醒来了...”,然后释放锁,此时线程B 才能获取锁并执行。
wait() - 线程的“耐心等待”
wait() 是 Object 类的一个实例方法,它不像 sleep() 那样设定闹钟,而是让你“耐心等待”,直到有人告诉你“可以继续了”(通过 notify() 或 notifyAll())。

核心特点:
- 释放锁:当一个线程调用
wait()时,它会立即释放当前对象锁,并进入该对象的“等待队列”(Wait Set)中,这是它与sleep()最根本的区别。 - 被动唤醒:线程不会自己醒来,它必须由另一个持有同一个锁的线程调用
notify()(随机唤醒一个等待的线程)或notifyAll()(唤醒所有等待的线程)来唤醒。 - 必须在同步块/方法中调用:因为
wait()会释放锁,所以它必须在持有锁的情况下才能被调用,否则会抛出IllegalMonitorStateException异常。
代码示例:
public class WaitExample {
private static final Object lock = new Object();
public static void main(String[] args) {
Runnable task = () -> {
synchronized (lock) { // 必须在同步块中调用 wait()
System.out.println(Thread.currentThread().getName() + " 获取到锁,但需要等待条件...");
try {
lock.wait(); // 释放锁,进入等待状态
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 收到通知,继续执行。");
}
};
new Thread(task, "等待线程A").start();
try {
Thread.sleep(1000); // 主线程睡1秒,确保等待线程A已经进入wait状态
} catch (InterruptedException e) {
e.printStackTrace();
}
// 另一个线程来唤醒它
Runnable notifierTask = () -> {
synchronized (lock) {
System.out.println("通知线程 获取到锁,准备唤醒等待的线程。");
lock.notify(); // 唤醒一个在lock对象上等待的线程
System.out.println("通知线程 已发出通知,但还未释放锁。");
}
// 同步块执行完毕,通知线程释放锁
System.out.println("通知线程 释放了锁。");
};
new Thread(notifierTask, "通知线程").start();
}
}
执行结果分析:
- 等待线程A 获取
lock对象的锁,打印“需要等待条件...”,然后调用lock.wait()。 - 关键点:等待线程A 立即释放了
lock锁,并进入等待状态。 - 通知线程可以轻松地获取到
lock锁。 - 通知线程调用
lock.notify(),这会唤醒等待线程A。 - 重要:被唤醒的线程(等待线程A)不会立即执行,它需要重新获取到
lock锁,由于此时通知线程还持有锁,所以等待线程A会进入“锁池”(Blocked on lock forlock)中等待。 - 当通知线程执行完
synchronized代码块并释放锁后,等待线程A才能获取锁,从wait()的下一句代码开始执行,打印“收到通知...”。
生动的比喻:咖啡厅排队
想象一下你在一个咖啡厅排队买咖啡。

-
sleep()就像是你排队时去趟洗手间。- 你暂时离开队伍(暂停执行)。
- 你并没有把你的位置让给别人(不释放锁),队伍里的人都知道你只是去一下,很快就会回来,所以没人会插你的队。
- 你设定了5分钟的时间(
sleep(5000)),5分钟后你自然回到队伍里(自动唤醒)。
-
wait()就像是你发现咖啡机坏了,决定去旁边的座位坐着等。- 你明确告诉排在你后面的人:“我不等了,你们先买吧”(释放锁)。
- 你走到一个专门的“等待区”(等待队列)坐下。
- 你不会自己站起来走回队伍,而是耐心等待咖啡店员(另一个线程)喊你的名字(
notify())或者喊所有等的人(notifyAll())。 - 当你被叫到名字时,你需要重新回到队伍的末尾(重新竞争锁),等待轮到你才能买咖啡(重新获取锁后继续执行)。
总结与何时使用
| 使用场景 | 推荐方法 | 原因 |
|---|---|---|
| 我只需要让当前线程暂停一段时间,不需要和其他线程交互。 | Thread.sleep() |
简单直接,不涉及复杂的同步和唤醒机制。 |
| 我需要让一个线程等待,直到另一个线程完成某个操作或某个条件变为真。 | Object.wait() / notify() |
这是 Java 中实现线程间通信和协作的标准机制,能高效地管理线程的生命周期。 |
记住黄金法则:
sleep()是 Thread 的方法,用于控制自身,不释放锁。wait()是 Object 的方法,用于线程间通信,必须释放锁。
