Java 中 wait 和 sleep 的区别:从原理到实践全解析
前言
在 Java 多线程编程中,wait
和 sleep
是两位重量级角色。尽管它们都可以让线程暂停执行,但在实际使用中却有着天壤之别。很多人初学时对这两者感到困惑,甚至在面试中被考官“灵魂拷问”:“wait
和 sleep
有什么区别?”。今天,我们就从它们的原理、使用场景、常见误区等方面做一个详细的讲解。
一、基本概念
1. sleep
方法
Thread.sleep(long millis)
是 Java 提供的线程暂停工具,用于让当前线程休眠指定时间。
- 属于
Thread
类 的静态方法。 - 不需要依赖同步锁。
- 休眠时间结束后,线程会自动从阻塞状态变为可运行状态。
示例:
System.out.println("开始休眠");
Thread.sleep(2000); // 当前线程休眠 2 秒
System.out.println("休眠结束");
2. wait
方法
wait()
是 Object 类的实例方法,用于让当前线程进入等待状态,直到被唤醒。
- 属于
Object
类 的实例方法。 - 必须在 同步代码块或同步方法 中调用,否则会抛出
IllegalMonitorStateException
。 - 等待的线程需要通过
notify()
或notifyAll()
唤醒。
示例:
synchronized (lock) {
System.out.println("线程进入等待状态");
lock.wait(); // 线程释放锁,进入等待状态
System.out.println("线程被唤醒");
}
二、核心区别
1. 所属类不同
方法 | 所属类 | 作用对象 |
---|---|---|
sleep | Thread | 当前线程 |
wait | Object | 持有对象锁的线程 |
2. 是否释放锁
方法 | 是否释放锁 |
---|---|
sleep | 不释放锁 |
wait | 释放锁 |
解释:
sleep
不会影响同步代码块中其他线程的执行,因为它只是让线程休眠,锁仍然被持有。wait
会主动释放锁,让其他线程有机会获取锁并执行。
3. 触发方式不同
方法 | 触发恢复方式 |
---|---|
sleep | 休眠时间结束后自动恢复 |
wait | 必须被 notify 或 notifyAll 唤醒 |
4. 使用场景不同
方法 | 使用场景 |
---|---|
sleep | 暂停线程,模拟延迟 |
wait | 线程间通信,等待某个条件满足时恢复 |
三、实践解析
1. sleep
的使用场景:模拟延迟
在某些场景中,我们需要让线程暂停一段时间,例如定时任务或动画效果的实现。
示例:
public class SleepDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println("倒计时开始:");
for (int i = 5; i > 0; i--) {
System.out.println(i);
Thread.sleep(1000); // 每隔 1 秒打印一次
}
System.out.println("倒计时结束!");
}
}
运行结果:
倒计时开始:
5
4
3
2
1
倒计时结束!
2. wait
的使用场景:线程间通信
在多线程编程中,wait
和 notify
是线程间通信的核心工具。例如,生产者-消费者模式中,生产者需要等待消费者处理完消息后继续生产。
示例:
public class WaitNotifyDemo {
private static final Object lock = new Object();
private static boolean condition = false;
public static void main(String[] args) {
Thread producer = new Thread(() -> {
synchronized (lock) {
System.out.println("生产者:准备生产数据");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
condition = true;
System.out.println("生产者:数据已就绪,唤醒消费者");
lock.notify();
}
});
Thread consumer = new Thread(() -> {
synchronized (lock) {
while (!condition) {
try {
System.out.println("消费者:数据未就绪,进入等待");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费者:数据已处理");
}
});
consumer.start();
producer.start();
}
}
运行结果:
消费者:数据未就绪,进入等待
生产者:准备生产数据
生产者:数据已就绪,唤醒消费者
消费者:数据已处理
四、常见误区
1. 在非同步块中调用 wait
wait
方法必须在同步代码块或同步方法中调用,否则会抛出异常。
错误示例:
Object lock = new Object();
lock.wait(); // 会抛出 IllegalMonitorStateException
正确示例:
synchronized (lock) {
lock.wait(); // 正常工作
}
2. 忘记调用 notify
或 notifyAll
使用 wait
后,必须确保在某个条件满足时调用 notify
或 notifyAll
唤醒线程,否则等待的线程将一直处于阻塞状态。
五、总结
特性 | sleep | wait |
---|---|---|
所属类 | Thread | Object |
是否释放锁 | 否 | 是 |
唤醒方式 | 自动 | notify 或 notifyAll |
使用场景 | 模拟延迟、定时任务 | 线程间通信、等待条件 |
记忆小贴士:
- 如果只是简单地让线程暂停一段时间,使用
sleep
。 - 如果需要线程之间协作,等待某个条件时,使用
wait
搭配notify
。
掌握了 wait
和 sleep
的区别后,相信你在面对多线程开发时会更加得心应手!如果你还有其他疑问,欢迎留言讨论~ 😊