在Java多线程编程中,wait和sleep都能让线程进入等待状态,但两者的底层实现、使用规则和线程状态变化逻辑存在明显差异,理解这些差异是写出正确多线程代码的基础。

wait和sleep的核心区别
我们可以从多个维度对比这两个方法的差异,具体如下表所示:
| 对比维度 | wait方法 | sleep方法 |
|---|---|---|
| 所属类 | Object类的方法 | Thread类的静态方法 |
| 锁释放规则 | 调用后会释放当前持有的对象锁 | 调用后不会释放任何锁 |
| 使用前提 | 必须在同步代码块或同步方法中调用,且要获取对应对象的锁 | 可以在任意位置调用,无锁要求 |
| 唤醒方式 | 依赖其他线程调用同一对象的notify或notifyAll方法,或者等待超时 | 等待指定时间后自动唤醒,或者被中断唤醒 |
| 线程状态变化 | 从RUNNABLE变为WAITING或TIMED_WAITING | 从RUNNABLE变为TIMED_WAITING |
代码示例对比
下面通过两段代码分别展示wait和sleep的使用场景:
wait方法使用示例
public class WaitDemo {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
synchronized (lock) {
System.out.println("线程1获取到锁,开始执行");
try {
// 调用wait释放锁,进入等待状态
lock.wait(2000);
System.out.println("线程1被唤醒,继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock) {
System.out.println("线程2获取到锁,开始执行");
// 唤醒等待在lock对象上的线程
lock.notify();
System.out.println("线程2调用notify唤醒其他线程");
}
});
thread1.start();
// 让线程1先执行
Thread.sleep(100);
thread2.start();
}
}
sleep方法使用示例
public class SleepDemo {
private static final Object lock = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (lock) {
System.out.println("线程1获取到锁,开始执行");
try {
// 调用sleep不会释放锁
Thread.sleep(2000);
System.out.println("线程1睡眠结束,继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(() -> {
// 等待线程1释放锁才能获取
synchronized (lock) {
System.out.println("线程2获取到锁,开始执行");
}
});
thread1.start();
// 让线程1先执行
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.start();
}
}
运行WaitDemo时,线程1调用wait后会释放lock锁,线程2可以获取到锁执行,之后线程2调用notify唤醒线程1,线程1重新竞争锁后继续执行。而运行SleepDemo时,线程1调用sleep期间不会释放lock锁,线程2需要等待线程1执行完同步块释放锁后才能执行。
Java线程状态变化说明
Java线程的生命周期共有6种状态,定义在Thread.State枚举中,分别是:
- NEW:线程创建后尚未启动的状态
- RUNNABLE:线程正在Java虚拟机中执行,或者正在等待操作系统分配CPU时间片
- BLOCKED:线程等待获取监视器锁(进入同步代码块时锁被其他线程持有)的状态
- WAITING:线程无限期等待其他线程执行特定操作的状态,比如调用
Object.wait()无超时参数、Thread.join()无超时参数 - TIMED_WAITING:线程在指定时间内等待的状态,比如调用
Thread.sleep(long)、Object.wait(long)、Thread.join(long) - TERMINATED:线程执行完毕或者因异常退出的终止状态
状态流转关系
线程状态的流转遵循以下规则:
- NEW状态的线程调用
start()方法后进入RUNNABLE状态 - RUNNABLE状态的线程尝试进入同步代码块,若锁被其他线程持有则进入BLOCKED状态,获取到锁后回到RUNNABLE状态
- RUNNABLE状态的线程调用无超时的
wait()、join()或者LockSupport.park()会进入WAITING状态,被其他线程唤醒(notify、notifyAll、unpark)后重新竞争锁,竞争成功回到RUNNABLE状态 - RUNNABLE状态的线程调用带超时的
sleep(long)、wait(long)、join(long)或者LockSupport.parkNanos()会进入TIMED_WAITING状态,等待超时或者被唤醒后回到RUNNABLE状态 - 线程执行完
run()方法或者抛出未捕获的异常,会进入TERMINATED状态
注意:WAITING和TIMED_WAITING状态的线程被唤醒后,不会立刻回到RUNNABLE状态,而是需要先重新获取对象锁(如果是通过wait进入的等待状态),获取到锁之后才会继续执行。
结合wait和sleep的使用场景,调用sleep时线程从RUNNABLE变为TIMED_WAITING,等待时间结束后自动回到RUNNABLE;调用wait时线程从RUNNABLE变为WAITING或TIMED_WAITING,同时释放锁,被唤醒后需要重新竞争锁才能回到RUNNABLE状态,这就是两者在状态变化上的核心差异。