java线程通信

一、wait /notify/notifyAll 底层原理

1. 核心前提

  1. 必须在synchronized 锁内部使用,否则直接抛 IllegalMonitorStateException
  2. 调用 wait() 会自动释放当前锁,进入对象的 WaitSet 等待池
  3. 被唤醒后不会立刻执行,需要重新竞争锁,抢到锁才会继续往下走

2. 方法作用

  • obj.wait()

    当前线程进入无限等待,释放锁,等待被唤醒;

    支持重载:带超时时间的限时等待。

  • obj.notify()

    随机唤醒 WaitSet 中一个等待线程。

  • obj.notifyAll()

    唤醒 WaitSet 全部等待线程,全员去竞争锁。

3. 致命缺点

  1. 唤醒不精准:只能随机 / 全部唤醒,无法指定唤醒某个线程
  2. 多生产多消费场景:容易虚假唤醒、唤醒不该唤醒的线程,造成低效 / 死锁
  3. 只能依附 synchronized 原生锁,功能单一,不支持超时、不支持多条件队列

4. 虚假唤醒

线程没有被 notify / 中断 / 超时,无缘无故从 wait 醒来

开发规范:wait 必须写在 while 循环里做条件判断,醒来二次校验条件。


二、Condition 精准唤醒(JUC 升级版)

1. 定位

ConditionLock(ReentrantLock) 配套的条件等待工具

用来彻底解决 wait/notify 唤醒模糊的问题。

2. 核心优势

  1. 精准唤醒:多个 Condition 隔离不同等待队列,唤醒指定业务线程
  2. 多条件队列:一把锁可以创建多个 Condition,各司其职
  3. 功能更强:支持可中断等待、超时等待、不响应中断等待
  4. 搭配 Lock 锁,灵活度远高于 synchronized + wait

3. 核心方法

  • condition.await() :等价 wait,释放锁、进入等待队列
  • condition.signal() :等价 notify,精准唤醒当前队列一个线程
  • condition.signalAll() :等价 notifyAll,唤醒当前队列全部线程

4. 底层关系

AQS 内部维护:

  • 同步队列:抢锁失败排队

  • 条件队列:Condition.await 等待的队列

    等待时:同步队列 → 条件队列;

    唤醒时:条件队列 → 同步队列 → 重新抢锁。


三、生产者消费者模型(两种实现)

方式 1:synchronized + wait/notifyAll 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.util.ArrayList;
import java.util.List;

// 共享缓冲区
class Buffer {
private final List<Integer> list = new ArrayList<>();
private static final int MAX_SIZE = 10;

// 生产
public synchronized void put(int num) throws InterruptedException {
// while 防止虚假唤醒
while (list.size() >= MAX_SIZE) {
this.wait();
}
list.add(num);
System.out.println(Thread.currentThread().getName() + " 生产:" + num);
// 唤醒所有,简单粗暴
this.notifyAll();
}

// 消费
public synchronized void take() throws InterruptedException {
while (list.isEmpty()) {
this.wait();
}
Integer val = list.remove(0);
System.out.println(Thread.currentThread().getName() + " 消费:" + val);
this.notifyAll();
}
}

缺点:

生产满了、消费空了,全都挤在同一个 WaitSet;

notifyAll 一窝蜂唤醒,大量无效竞争,性能差。


方式 2:ReentrantLock + Condition 精准唤醒(推荐)

两个条件队列

  • notFull:缓冲区未满 → 生产者等待 / 唤醒
  • notEmpty:缓冲区不为空 → 消费者等待 / 唤醒
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

class ConditionBuffer {
private final List<Integer> list = new ArrayList<>();
private static final int MAX_SIZE = 10;
private final ReentrantLock lock = new ReentrantLock();

// 两个独立条件队列:精准分流
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();

// 生产者
public void put(int num) throws InterruptedException {
lock.lock();
try {
while (list.size() >= MAX_SIZE) {
// 队列满:生产者等待
notFull.await();
}
list.add(num);
System.out.println(Thread.currentThread().getName() + " 生产:" + num);
// 精准唤醒消费者
notEmpty.signal();
} finally {
lock.unlock();
}
}

// 消费者
public void take() throws InterruptedException {
lock.lock();
try {
while (list.isEmpty()) {
// 队列空:消费者等待
notEmpty.await();
}
Integer val = list.remove(0);
System.out.println(Thread.currentThread().getName() + " 消费:" + val);
// 精准唤醒生产者
notFull.signal();
} finally {
lock.unlock();
}
}
}

核心优势

  1. 队列满了,只阻塞生产者;只唤醒消费者
  2. 队列空了,只阻塞消费者;只唤醒生产者
  3. 无无效唤醒、无无效锁竞争,并发性能更高

四、核心对比 & 原理总结

  1. wait/notify

    绑定对象 Monitor 的 WaitSet,唤醒随机 / 全体,粒度粗、低效,依赖 synchronized。

  2. Condition/await/signal

    绑定 AQS 条件队列,多队列隔离、精准唤醒,搭配 Lock,功能更强、工业级使用。

  3. 生产者消费者核心规范

    • 条件等待必须用 while 循环,防御虚假唤醒
    • 等待前:判断阻塞条件
    • 唤醒后:二次校验条件,避免并发错乱
  4. 本质通信逻辑

    多线程争抢同一共享资源 → 不满足条件就释放锁并等待 → 条件满足被唤醒 → 重新抢锁执行业务。


java线程通信
http://hanqichuan.com/2026/04/16/java并发/java线程通信/
作者
韩启川
发布于
2026年4月16日
许可协议