Java-并发-线程的状态以及wait()、notify()和notifyAll()
0.是什么(What)
wait(), notify(), 和 notifyAll()方法都是Object类的一部分,用于实现线程间的协作。
1.为什么(Why)
线程的执行顺序是随机的(操作系统随机调度的,抢占式执行),但是有时候,我们希望的是它们能够顺序的执行。
所以引入了这几个方法,使得我们能保证一定的顺序。
1.1 Objec类
在 Java 中,所有对象都可以作为同步的监视器锁,即:何对象都可以在 synchronized 块或方法中被用作锁。
通过继承的特性,放在Object类中,无论哪个类的实例,都可以使用这些方法进行线程间通信。
1.2 历史原因
在 Java 1.0 版本发布时,Java 的并发机制主要依赖于 synchronized 关键字和 Object 类中的 wait(), notify(), 和 notifyAll() 方法。
这种设计虽然在高级并发控制工具引入后显得基础,但依然是 Java 语言设计的一部分,保持了向后兼容性和面向对象设计的一致性。
2.怎么用(How)
查看Object类,可以看到这几个与线程同步与通信相关的方法。
-
notify()、notifyAll()、wait(long timeout)都是
final + native的 -
wait()和wait(long timeout, int nanos)则是基于
wait(long timeout)的重载
public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;
public final void wait() throws InterruptedException {
wait(0);
}
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0) {
timeout++;
}
wait(timeout);
}
由于Object是顶层父类,所以任意变量实例化后,都会自动继承其中的方法,wait()、notify()、notifyAll()亦是如此,任何实例对象都会自动具备。

2.1 线程的状态
在Thread中,定义了一个枚举State,描述了线程的几种状态。

public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
6态关系,如下图所示。
怎么记忆:3+2+1
- (3种)正常情况下:新建 -> 可运行 -> 终止
- (2种)涉及到2种等待状态:等待 or 超时等待
- (1种)阻塞状态
这里的可运行如何理解?
处于可运行状态的线程正在 Java 虚拟机中执行,具体有没有执行要看操作系统。
| 序号 | 线程状态 | 描述 |
|---|---|---|
| 1 | 新建(New) | 线程对象被创建后,但尚未调用 start() 方法。 |
| 2 | 可运行(Runnable) | 调用了 start() 方法后,线程进入可运行(就绪)状态,等待CPU调度执行。 |
| 3 | 阻塞(Blocked) | 线程等待获取监视器锁,试图进入同步方法或同步块时。 |
| 4 | 等待(Waiting) | 线程等待另一个线程显式地唤醒它,例如调用 Object.wait() 或 Thread.join() 方法。 |
| 5 | 计时等待(Timed Waiting) | 线程等待指定时间或被唤醒,例如调用 Thread.sleep(long millis) 或 Object.wait(long timeout) 方法。 |
| 6 | 终止(Terminated) | 线程执行完毕或因异常退出,线程生命周期结束。 |

看上面的图,我们很好理解到,核心点在于Runnable向其他状态的转义。
开始、运行和终止很好理解,关键在于理解等待、超时等待和阻塞。
2.1.1 New
-
描述:线程对象已经创建,但尚未启动。
-
特点:线程处于新建状态,此时还未调用
start()方法。Thread thread = new Thread(() -> { // 线程任务 });
2.1.2 Runnable
-
描述:线程已经启动,正在运行或准备运行。
-
特点:调用
start()方法后,线程进入Runnable状态。此状态下的线程可能正在运行,也可能在等待操作系统为其分配CPU时间片。thread.start(); // 启动线程
2.1.3 Terminated
-
描述:线程已经完成执行。
-
特点:线程的
run()方法执行完毕或线程因为异常而终止。// 线程任务执行完毕
2.1.4 Waiting
-
描述:线程正在等待其他线程显式地唤醒。
-
特点:线程进入等待状态需要调用以下方法之一:
Object.wait()、Thread.join()、LockSupport.park()。synchronized (obj) { obj.wait(); // 进入等待状态 }
2.1.5 Timed_Waiting
-
描述:线程正在等待一段指定的时间。
-
特点:线程进入超时等待状态需要调用带有超时参数的方法,如
Thread.sleep(long millis)、Object.wait(long timeout)、Thread.join(long millis)、LockSupport.parkNanos(long nanos)、LockSupport.parkUntil(long deadline)。Thread.sleep(2000); // 休眠2秒
2.1.6 Blocked
-
描述:线程试图获取一个被其他线程持有的锁而被阻塞。
-
特点:线程在进入同步块或同步方法时,如果锁被其他线程持有,会进入
Blocked状态,直到获取到锁。synchronized (obj) { // 尝试获取锁 }
2.2 关键点
2.2.1 等待和超时等待
在Java中,等待状态(Waiting)和超时等待状态(Timed Waiting)都是线程的非运行状态,意味着线程不会占用CPU时间。
A.等待Waiting
- 无法自动唤醒:线程进入等待状态后,必须依赖其他线程显式地唤醒它。
- 无超时:线程将一直处于等待状态,直到被唤醒。
| 方法 | 描述 | 是否释放锁 | 必须在同步块中使用 |
|---|---|---|---|
| Object.wait() | 线程进入等待状态,直到其他线程调用 notify()/notifyAll() 唤醒。 | ✅ | ✅ |
| Thread.join() | 当前线程等待目标线程执行完毕。 | ❌ | ❌ |
| LockSupport.park() | 当前线程挂起,直到被其他线程调用 unpark() 唤醒。 | ❌ | ❌ |
B.超时等待Timed Waiting
- 自动唤醒:线程进入超时等待状态后,如果没有被显式唤醒,会在指定时间到后自动唤醒。
- 有限等待时间:线程将在设定的时间期限到达后自动从超时等待状态中退出。
| 方法 | 描述 | 是否释放锁 | 必须在同步块中使用 |
|---|---|---|---|
| Thread.sleep(millis) | 线程休眠指定毫秒数,到时间后自动恢复,不释放锁。 | ❌ | ❌ |
| Object.wait(timeout) | 线程等待指定时间,超时后自动恢复,期间释放锁。 | ✅ | ✅ |
| Thread.join(millis) | 当前线程等待目标线程完成或超时结束,不释放锁。 | ❌ | ❌ |
| LockSupport.parkNanos(nanos) | 挂起线程指定纳秒时间后自动恢复。 | ❌ | ❌ |
| LockSupport.parkUntil(deadline) | 挂起线程直到特定时间点后自动恢复。 | ❌ | ❌ |
C.综合对比
| 对比项 | 等待(Waiting) | 超时等待(Timed Waiting) |
|---|---|---|
| 特点 | 需要其他线程显式唤醒,否则线程将无限期等待,直到被唤醒 | 指定等待时间,时间到达或被唤醒后自动退出等待状态 |
| 进入方式 | wait()、join()、park() | wait(timeout)、sleep()、join(timeout) |
| 是否释放锁 | 是(如 wait) | 取决于方法:wait(timeout) 释放;sleep/join 不释放 |
| 是否需要唤醒 | 是,需 notify/notifyAll | 否,时间到自动恢复(也可以被唤醒) |
| 是否限时 | 否,无限期等待 | 是,等待时间有限 |
| 场景 | - 生产者-消费者模型中消费者等待生产者通知 - 多线程协作,如等待其他线程完成任务 |
- 网络编程中的超时操作 - 定时任务 - 获取锁时的超时重试 |
| 对象锁要求 | 是(如 wait 需 synchronized) | 不一定,如 sleep 不需要 |
下面是一个示例:
@Slf4j
public class WaitNotifyExample {
private static final Object lock = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lock) {
try {
log.info("线程1:等待通知");
lock.wait(); // 进入等待状态,并释放锁
log.info("线程1:收到通知");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock) {
log.info("线程2:持有锁并休眠");
try {
Thread.sleep(2000); // 持有锁2秒钟
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
lock.notify(); // 通知等待的线程
log.info("线程2:通知等待的线程");
}
});
t1.start();
t2.start();
}
}
这段代码中:
- 线程1和线程2持有同一个对象锁
lock,线程1进入wait()后,释放当前锁。 - 线程2拿到锁后,休眠2s后主动醒过来,然后唤醒线程1,最后线程1输出收到了通知。

2.2.2 等待和阻塞
2.3 状态切换
由上文可知,等待状态(Waiting)如果不主动唤醒,的线程可能会一直处于等待状态,永远不会被唤醒。
这意味着该线程将一直保持阻塞状态,不会继续执行其后续代码,且不会释放它所持有的任何锁。
这种情况可能会导致以下问题:
-
线程泄漏:等待状态的线程如果没有被唤醒,可能会占用系统资源而不做任何有用的工作,导致线程泄漏。
-
死锁:如果一个线程在等待状态中持有某些关键资源(例如锁),其他线程可能无法获取这些资源,导致死锁。
-
程序挂起:如果程序中有重要任务依赖于等待状态的线程被唤醒,那么程序的某些部分可能会永远无法执行,导致程序挂起或不响应。
2.3.1 wait()、notify()、notifyAll()
| 序号 | 方法 | 定义 | 使用场景 |
|---|---|---|---|
| 1 | wait() | 使当前线程等待,直到其他线程调用notify()或notifyAll()方法,或线程被中断 |
线程等待某个条件满足 |
| 2 | notify() | 唤醒在此对象监视器上等待的单个线程。 如果有多个线程在等待,则随机选择其中一个被唤醒。 |
通知一个等待线程的条件已经满足 |
| 3 | notifyAll() | 唤醒在此对象监视器上等待的所有线程。 被唤醒的线程会去竞争对象的锁,只有获得锁的线程才能继续执行 |
通知所有等待线程的条件已经满足 |
2.3.2 对象的锁状态
wait(), notify(), 和 notifyAll() 的使用主要是为了实现线程间的协作和同步,具体是否需要使用这些方法取决于是否需要当前线程进行等待和唤醒。
附:在Java-线程-synchronized这篇文章中,我们描述到Java对象的锁状态有以下几种。

| 序号 | 锁类型 | 场景 | 实现 | 位置 | 优点 | 缺点 |
|---|---|---|---|---|---|---|
| 0 | 无锁 | - | 对象没有被任何线程持有锁,所有线程都能自由访问对象,无需任何同步机制。 | - | - | - |
| 1 | 偏向锁 | 同一线程多次进入同步块时(没有竞争) | 在对象头中记录偏向线程的ID,如果同一线程再次进入同步块,直接进入,无需CAS操作。 | 用户态 | 锁开销极低,适用于无竞争的情况 | 一旦有其他线程竞争,需等待偏向撤销,适用于线程间少量竞争的场景。 |
| 2 | 轻量级锁 | 锁定时间短,发生竞争时使用 | 线程尝试通过CAS操作获取锁,如果失败则自旋等待一段时间。 | 用户态 | 避免了线程切换的开销,自旋等待有机会快速获得锁。 | 自旋等待会消耗CPU时间,不适用于长时间持锁的情况。 |
| 3 | 重量级锁 | 线程竞争严重或持锁时间较长 | 通过操作系统的互斥量(Mutex)实现,线程竞争锁失败时会被挂起。 | 内核态 | 线程挂起等待锁释放,不消耗CPU时间 | 线程挂起和恢复的开销较大,适用于长时间持锁的情况。 |
3.常见问题
3.1 sleep方法跟那个wait方法有什么区别啊?
| 序号 | 特性 | sleep() |
wait() |
|---|---|---|---|
| 1 | 所属类 | Thread | Object |
| 2 | 是否释放锁 | 否 | 是 |
| 3 | 是否需要在同步块中使用 | 否 | 是 |
| 4 | 用途 | 暂停当前线程指定时间 | 等待其他线程的通知或超时唤醒 |
| 5 | 是否用于线程间通信 | 否 | 是 |
| 6 | 调用方式 | 静态方法 | 实例方法 |
-
关于是否释放锁
sleep()方法- 不释放锁:当一个线程调用
sleep()方法时,线程会进入休眠状态,但它依然持有任何已经持有的锁。这意味着其他线程无法获得这些锁,直到休眠线程醒来并释放锁。 - 影响:由于
sleep()方法不释放锁,使用它进行线程间的协调时需要小心。如果一个线程在持有锁的情况下调用sleep()方法,可能会导致其他需要相同锁的线程被阻塞,进而导致性能问题或死锁。
- 不释放锁:当一个线程调用
wait()方法- 释放锁:当一个线程调用
wait()方法时,它会释放当前持有的锁,并进入等待状态。这样其他线程可以获取到这个锁并执行相应的操作。 - 影响:
wait()方法的设计初衷是用于线程间的通信和协调。通过释放锁,其他线程可以继续执行,从而实现资源共享和线程间协作。例如,在生产者-消费者模型中,消费者在等待新的数据时调用wait()方法释放锁,让生产者可以继续生产数据。
- 释放锁:当一个线程调用
-
关于是否需要在同步块中使用
sleep()方法- 不需要在同步块中使用:
sleep()方法可以在任何地方调用,不需要在同步块或同步方法中。它只是让当前线程暂停执行指定时间,与线程同步机制无关。 - 影响:由于
sleep()方法不涉及锁的释放与获取,它在设计上与同步机制无关。你可以在同步块内外调用sleep()方法,但需要注意的是,如果在同步块内调用sleep()方法,线程在休眠期间依然持有锁。
- 不需要在同步块中使用:
wait()方法- 必须在同步块中使用:
wait()方法必须在同步块或同步方法中调用。调用wait()方法的线程必须持有调用对象的监视器锁(即对象锁),否则会抛出IllegalMonitorStateException异常。 - 影响:
wait()方法的设计初衷是用于线程间的通信和协调。只有在同步 - 中调用
wait()方法,才能确保线程在进入等待状态之前持有对象的监视器锁,并在调用wait()时释放该锁,使其他线程能够获得该锁进行相应操作。
- 必须在同步块中使用:
好,这里你就可以简单的理解为,因为sleep不释放锁,而wait释放锁,所以呢,为了保证wait能有锁来释放,所以说wait必须在同步代码块中。
参考下我这篇文章,了解下锁机制:Java-线程-synchronized
3.2 多线程的wait、notify、notifyAll方法为何放在Object上而不是Thread上
在Java的早期,咱们的同步机制是通过synchronized来实现的,从上文中可以知道,这个玩意底层是跟监视器锁有关的。
当一个线程进入 synchronized 块时,它会持有该对象的监视器锁
这个时候,如何去让出执行权呢?咱们就需要一个机制,释放掉这个监视器锁,这个就是wait的用处。
3.3 进入哪个状态和锁的释放,并没有直接的关系。
从前面的内容中我们可以知道:
-
wait(timeout)会进入超时等待状态,会释放锁。
-
sleep()也会进入超时等待状态,不会释放锁。
之前我这里很混乱,一直很懵,进入超时等待状态到底释不释放锁呢?
后面我才意识到,是我理解的有问题。
线程进入哪种状态(Waiting、Timed Waiting、Blocked)和是否释放锁 ——之间没有必然的因果关系,它们是两个不同的概念维度。
也就是说,释不释放锁跟咱们这个方法的底层实现有关系,而不是强制的要跟线程状态关联起来。
| 方法 | 状态 | 是否释放锁 | 原因说明 |
|---|---|---|---|
| Thread.sleep() | Timed Waiting | ❌ | 与锁无关,不依赖对象监视器 |
| Object.wait() | Waiting | ✅ | 依赖 monitor,进入等待前自动释放当前对象锁 |
| Object.wait(timeout) | Timed Waiting | ✅ | 同上,但带有超时 |
| Thread.join() | Waiting | ❌ | 等待另一个线程结束,不会释放当前对象锁 |
| Thread.join(timeout) | Timed Waiting | ❌ | 同上 |
| 被 synchronized阻塞 | Blocked | ❌ | 根本没获得锁,谈不上释放 |

浙公网安备 33010602011771号