Java 并发编程:wait/notify/notifyAll 原理与使用场景全梳理
前言:
- sleep(1000) 是 Thread 中的方法,参数是sleep多少毫秒。sleep() 的作用是将当前线程暂停一段时间,但这期间不会释放锁
- wait、notify、notifyAll 是 Object 中的方法,可以作用于任何对象,用于控制线程的状态,通常配合 synchronized 代码块使用
一、先分清核心定位:
sleep 是 “休眠”,wait/notify/notifyAll 是 “线程通信”
- sleep:属于
Thread类的静态方法,核心是让线程 “休眠” 指定时间,是线程自身的状态控制; - wait/notify/notifyAll:属于
Object类的方法(所有对象都有),核心是实现线程间的通信与协作(比如生产者 - 消费者模型),依赖对象的内置锁(Monitor)。
二、sleep 详解(线程休眠,不释放锁)
通俗理解:就像你在办公室里锁着门加班,定了 10 分钟闹钟后趴在桌上休息 —— 门还锁着(持有锁),闹钟响了(时间到)才起来继续干活。
专业核心点:
-
归属:
Thread.sleep(long millis),静态方法,只能让当前执行线程休眠; -
锁行为:休眠期间不会释放持有的任何锁(比如 synchronized 锁);
-
异常处理:必须捕获 / 声明
InterruptedException(休眠时线程可能被中断); -
状态变化:线程从「运行态」→「阻塞态」,时间到后回到「就绪态」等待 CPU 调度;
-
使用场景:模拟延迟(比如接口重试间隔)、降低线程执行频率(定时轮询)。
-
sleep 的作用是使当前线程睡眠指定的时间,放弃对CPU的占用,但是不会放弃对线程以获取到的锁,例:
public class SleepTest extends Thread{
// 锁
public static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
SleepTest sleepTest = new SleepTest();
sleepTest.setName("test1");
sleepTest.start();
SleepTest sleepTest2 = new SleepTest();
sleepTest2.setName("test2");
sleepTest2.start();
}
@Override
public void run() {
System.out.println("ThreadName :" + Thread.currentThread().getName() + " start" );
synchronized (lock){
for (int i =0 ; i<5; i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("ThreadName :" + Thread.currentThread().getName() + "; i == " + i);
}
}
}
}
打印结果:
ThreadName :test2 start
ThreadName :test1 start
ThreadName :test2; i == 0
ThreadName :test2; i == 1
ThreadName :test2; i == 2
ThreadName :test2; i == 3
ThreadName :test2; i == 4
ThreadName :test1; i == 0
ThreadName :test1; i == 1
ThreadName :test1; i == 2
ThreadName :test1; i == 3
ThreadName :test1; i == 4
在main方法中我们,我们创建了两个线程,它们共同竞争同一把锁 lock ,根据打印结果可以看出,线程1 一直等到线程2 执行结束之后才执行 synchronized 中代码块, sleep(1000) 方法在线程2 中并没有释放 锁lock ,直至执行结束
三、等待池、锁池简介
了解这个wait、notify、notifyAll三个方法之前,需要先了解两个概念:等待池、锁池;
- 等待池:当对象锁【lock】 调用 wait 方法时, 持有该对象锁的线程会进入到等待池中,并且释放当前对象锁,线程进入到被动状态,需要notify notifyAll 将其唤醒。
- 锁池: 假设某对象锁当前某线程获取,其他线程想要持有该对象锁时,需要进入到锁池中,等待对象锁被释放再去竞争锁,线程进入到主动状态,等待对象锁空闲。
四、wait 详解(线程等待,释放锁)
通俗理解:你在办公室锁着门干活,突然发现缺材料,于是把门锁打开(释放锁),走到茶水间等同事送材料(进入等待队列)—— 直到同事喊你(notify),你再回去抢门锁(重新竞争锁)继续干活。
专业核心点:
- 归属:
Object.wait()/wait(long timeout),实例方法,必须在synchronized同步块 / 方法中调用(否则抛IllegalMonitorStateException); - 锁行为:调用瞬间立刻释放持有的对象锁(这是和 sleep 最核心的区别);
- 状态变化:线程从「运行态」→「等待态」,进入该对象的 Monitor 等待队列;
- 唤醒条件:
-
- 其他线程调用该对象的
notify()/notifyAll(); - 等待超时(调用
wait(long)时); - 线程被中断(调用
interrupt());
- 其他线程调用该对象的
- 唤醒后行为:线程不会立刻执行,需重新竞争对象锁,拿到锁后才会从
wait()处继续执行; - 使用场景:线程间依赖等待(比如生产者没生产,消费者等待)。
synchronized (lock) {
while (条件不满足) { // 用while防止虚假唤醒,面试加分点
lock.wait(); // 释放lock锁,进入等待队列
}
// 条件满足,执行业务逻辑
}
五、notify/notifyAll 详解(唤醒等待线程)
通俗理解:notify 是 “喊一个人”(随机唤醒等待队列中的一个线程),notifyAll 是 “喊所有人”(唤醒等待队列中的所有线程)—— 被喊的人需要重新抢门锁(竞争锁)才能干活。
1. notify()
专业核心点:
- 归属:
Object.notify(),必须在synchronized同步块 / 方法中调用; - 作用:随机唤醒该对象 Monitor 等待队列中的一个线程,被唤醒的线程进入「就绪态」,需重新竞争锁;
- 注意:调用后当前线程不会立刻释放锁,需执行完同步代码块 / 方法才释放。
2. notifyAll()
专业核心点:
- 作用:唤醒该对象 Monitor 等待队列中的所有线程,所有线程进入「就绪态」竞争锁,最终只有一个线程能拿到锁执行;
- 对比 notify ():notify () 可能导致 “虚假唤醒”(比如多个消费者等待,只唤醒一个但条件仍不满足),生产环境优先用 notifyAll ();
- 使用场景:多线程等待同一条件(比如多个消费者等待生产者生产)。
代码示例(生产者唤醒消费者):
synchronized (lock) {
// 生产数据,满足条件
lock.notifyAll(); // 唤醒所有等待的消费者线程
}
- wait、notify、notifyAll 需要配合 synchronized (lock) 来使用,因为对对象锁的进行操作之前需要,对其先进行监听操作。
- wait() : 会将当前线程进入休眠状态,并且释放当前获取到的对象锁,并将当前线程放入到等待池中,等待notify、notifyAll 将其唤醒并进入锁池中竞争对象锁,
- notify 从等待池中随机选择【和优先级也有关】唤醒其中一个线程,进入到锁池中等待对象锁,并执行,其他等待池中线程继续等待。
- notifyAll 将等待池中所有的线程全部唤醒到锁池中,然后去竞争对象锁,
例 :
六、测试
四种综合
public class WaitTest extends Thread{
// 锁
public static final Object lock = new Object();
public static final Object lock1 = new Object();
public static void main(String[] args) throws InterruptedException {
// 创建线程一
WaitTest sleepTest = new WaitTest();
sleepTest.setName("test1");
sleepTest.start();
// 创建线程二
WaitTest sleepTest2 = new WaitTest();
sleepTest2.setName("test2");
sleepTest2.start();
// sleep 保证线程一、二调用 wait 进入到等待池中
Thread.sleep(1000);
synchronized (lock){
// 随机唤醒一个线程进入到锁池中等待对象锁
System.out.println("lock.notify");
lock.notify();
}
}
@Override
public void run() {
synchronized (lock){
try {
System.out.println("Thread-Name :" + Thread.currentThread().getName() + " wait ");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i =0 ; i< 5; i++){
System.out.println("Thread-Name :" + Thread.currentThread().getName() + "; i == " + i);
}
}
}
}
打印结果:
Thread-Name :test2 wait
Thread-Name :test1 wait
lock.notify
Thread-Name :test2; i == 0
Thread-Name :test2; i == 1
Thread-Name :test2; i == 2
Thread-Name :test2; i == 3
Thread-Name :test2; i == 4
主线程sleep(1000) 期间,线程一二都通过 wait 进入到等待池中,在sleep() 之后,调用lock.notify(),将线程二唤醒进入到锁池中,线程一继续等待被唤醒。
- lock.notify(); 换成 lock.notifyAll(); 之后打印结果:
Thread-Name :test1 wait
Thread-Name :test2 wait
lock.notifyAll
Thread-Name :test2; i == 0
Thread-Name :test2; i == 1
Thread-Name :test2; i == 2
Thread-Name :test2; i == 3
Thread-Name :test2; i == 4
Thread-Name :test1; i == 0
Thread-Name :test1; i == 1
Thread-Name :test1; i == 2
Thread-Name :test1; i == 3
Thread-Name :test1; i == 4
根据结果可以看出,线程一二全部被唤醒,等待 lock 锁之后全部执行完毕。
生产者消费模型
通俗解释:生产者和消费者共用一个 “货架(队列)”,货架满了生产者就 wait(释放锁),等消费者拿走产品后唤醒;货架空了消费者就 wait(释放锁),等生产者放产品后唤醒,实现线程间的协调。
生产者消费者模型是 Java 并发编程中最经典的线程通信场景,核心目的是协调 “生产资源” 和 “消费资源” 的线程,避免生产过剩或消费空资源。
我用大白话解释:
- 核心角色:
-
- 生产者线程:负责 “生产” 数据 / 资源(比如往队列里放产品);
- 消费者线程:负责 “消费” 数据 / 资源(比如从队列里取产品);
- 缓冲容器:通常是队列 / 数组(相当于 “货架”),用来存生产的资源,解耦生产者和消费者的速度差异。
- 核心问题:
-
- 生产者不能往满的缓冲容器里生产(否则溢出);
- 消费者不能从空的缓冲容器里消费(否则没东西可拿)。
- 解决方式:用
wait()和notify()/notifyAll()实现线程间通信:
-
- 缓冲容器满时,生产者调用
wait()释放锁,进入等待状态; - 消费者消费后,调用
notify()唤醒生产者继续生产; - 缓冲容器空时,消费者调用
wait()释放锁,进入等待状态; - 生产者生产后,调用
notify()唤醒消费者继续消费。
- 缓冲容器满时,生产者调用
简单总结:生产者和消费者通过缓冲容器隔离,靠wait()/notify()协调节奏,避免 “生产到满还在生产”“消费到空还在消费” 的问题。
import java.util.LinkedList;
import java.util.Queue;
// 生产者消费者模型
public class ProducerConsumer {
// 产品队列(最多存5个)
private static final Queue<String> QUEUE = new LinkedList<>();
private static final int MAX_SIZE = 5;
private static final Object LOCK = new Object();
// 生产者:生产产品
static class Producer extends Thread {
@Override
public void run() {
while (true) {
synchronized (LOCK) {
// 队列满了,等待消费者消费
while (QUEUE.size() == MAX_SIZE) {
try {
System.out.println("生产者:队列满了,等待消费");
LOCK.wait(); // 释放锁,等消费者唤醒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 生产产品
String product = "产品" + System.currentTimeMillis();
QUEUE.add(product);
System.out.println("生产者:生产了" + product + ",队列长度:" + QUEUE.size());
LOCK.notifyAll(); // 唤醒消费者
}
// 模拟生产耗时
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 消费者:消费产品
static class Consumer extends Thread {
@Override
public void run() {
while (true) {
synchronized (LOCK) {
// 队列空了,等待生产者生产
while (QUEUE.isEmpty()) {
try {
System.out.println("消费者:队列空了,等待生产");
LOCK.wait(); // 释放锁,等生产者唤醒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 消费产品
String product = QUEUE.poll();
System.out.println("消费者:消费了" + product + ",队列长度:" + QUEUE.size());
LOCK.notifyAll(); // 唤醒生产者
}
// 模拟消费耗时
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
new Producer().start();
new Consumer().start();
}
}

七、总结
| 维度 | sleep | wait |
|---|---|---|
| 所属类 | Thread(静态) | Object(实例) |
| 锁释放 | 不释放 | 立刻释放 |
| 调用位置 | 任意位置 | 必须在 synchronized 同步块 / 方法 |
| 唤醒方式 | 时间到自动唤醒 | 需 notify/notifyAll/ 超时 / 中断 |
| 异常处理 | 需处理 InterruptedException | 需处理 InterruptedException |
| 维度 | notify() | notifyAll() |
|---|---|---|
| 唤醒数量 | 随机唤醒 1 个等待线程 | 唤醒所有等待线程 |
| 竞争成本 | 低(仅一个线程竞争锁) | 高(所有线程竞争锁) |
| 风险 | 易出现虚假唤醒 | 无虚假唤醒风险 |
| 适用场景 | 单生产者 - 单消费者 | 多生产者 - 多消费者(生产环境首选) |
- wait 必须用 while 循环判断条件:不能用 if—— 因为线程被 notify () 唤醒后,条件可能已经被其他线程修改(虚假唤醒),while 会重新检查条件,if 只会检查一次;
- notify () 后不会立刻释放锁:比如线程 A 调用
lock.notify()后,需执行完同步代码块,才会释放 lock 锁,线程 B(被唤醒的)才能竞争锁; - sleep 不释放锁的坑:若在同步块中调用 sleep,其他线程会一直阻塞,直到 sleep 结束、当前线程释放锁,这是新手常犯的性能问题;
- wait/notify 必须对应同一个对象:线程 A 调用
obj1.wait(),必须由其他线程调用obj1.notify()才能唤醒,调用obj2.notify()无效。
整体总结
sleep 是线程自身的休眠控制,不释放锁,适合简单的延迟场景;wait/notify/notifyAll 是线程间通信的核心,依赖对象锁,wait 释放锁、notify/notifyAll 唤醒等待线程,是实现生产者 - 消费者等协作模型的基础。
核心记住三个关键点:
- sleep 归 Thread,wait/notify 归 Object;
- sleep 不释放锁,wait 释放锁;
- wait/notify 必须在 synchronized 中调用,生产环境优先用 notifyAll () + while 循环防虚假唤醒。
更多推荐




所有评论(0)