一、线程间通信核心概念
线程间通信是多线程编程的核心难点之一,其本质是协调多个线程的执行顺序,实现数据共享和逻辑同步。Java 中实现线程通信的核心机制是等待 - 唤醒模型,主要通过以下两种方式实现:
- synchronized + Object.wait()/notify()/notifyAll():基于原生同步锁的通信方式
- Lock + Condition.await()/signal()/signalAll():基于 JUC 的更灵活的通信方式
二、基础案例:线程交替操作变量
2.1 需求说明
两个线程操作一个初始值为 0 的变量,一个线程对变量 + 1,一个线程对变量 - 1,交替执行 10 轮。
2.2 初始实现(存在隐患)
class ShareData {
private Integer number = 0;
/**
* 变量+1操作
*/
public synchronized void increment() throws InterruptedException {
// 判断:不满足条件则等待
if (number != 0) {
this.wait();
}
// 干活:执行核心逻辑
number++;
System.out.println(Thread.currentThread().getName() + ": " + number);
// 通知:唤醒其他等待线程
this.notifyAll();
}
/**
* 变量-1操作
*/
public synchronized void decrement() throws InterruptedException {
// 判断
if (number != 1) {
this.wait();
}
// 干活
number--;
System.out.println(Thread.currentThread().getName() + ": " + number);
// 通知
this.notifyAll();
}
}
public class NotifyWaitDemo {
public static void main(String[] args) {
ShareData shareData = new ShareData();
// +1线程
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
shareData.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "AAA").start();
// -1线程
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
shareData.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "BBB").start();
}
}
2.3 虚假唤醒问题
当线程数量扩展到 4 个(2 个 + 1 线程,2 个 - 1 线程)时,上述代码会出现虚假唤醒问题,导致变量值错乱(如出现 2、3 甚至负数)。
问题根源
- wait () 方法被唤醒后,会从等待处继续执行,而不是重新判断条件
- if 判断只会执行一次,无法处理多次唤醒的场景

解决方案:使用 while 代替 if
// 修正后的判断逻辑
while (number != 0) {
this.wait();
}
while (number != 1) {
this.wait();
}

2.4 使用 Condition 实现通信
对标synchronized:
synchronized是原生的类,jdk当中本身就自带的。所以可以用wait和notify来进行等待和唤醒。但是lock是juc当中的,有自己的一套等待和唤醒的方法。

Condition 是 Lock 锁的配套通信工具,相比 Object 的 wait/notify,提供了更灵活的线程控制能力:
class ShareData {
private Integer number = 0;
// 创建可重入锁
private final Lock lock = new ReentrantLock();
// 创建Condition对象
private final Condition condition = lock.newCondition();
/**
* 变量+1操作
*/
public void increment() throws InterruptedException {
lock.lock(); // 加锁
try {
// 判断:防止虚假唤醒
while (number != 0) {
condition.await(); // 替代wait()
}
// 干活
number++;
System.out.println(Thread.currentThread().getName() + ": " + number);
// 通知
condition.signalAll(); // 替代notifyAll()
} finally {
lock.unlock(); // 解锁
}
}
/**
* 变量-1操作
*/
public void decrement() throws InterruptedException {
lock.lock();
try {
while (number != 1) {
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName() + ": " + number);
condition.signalAll();
} finally {
lock.unlock();
}
}
}

三、进阶实战:定制化线程通信
3.1 需求说明
实现三个线程按顺序执行:
- AA 线程打印 5 次
- BB 线程打印 10 次
- CC 线程打印 15 次
- 循环执行 10 轮
3.2 实现思路
- 使用 ReentrantLock 保证线程安全
- 为每个线程创建独立的 Condition 对象
- 通过标志位控制线程执行顺序
- 执行完成后修改标志位并唤醒下一个线程
3.3 完整代码
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadOrderAccess {
// 全局锁对象
private final Lock lock = new ReentrantLock();
// 为每个线程创建独立的Condition
private final Condition conditionA = lock.newCondition();
private final Condition conditionB = lock.newCondition();
private final Condition conditionC = lock.newCondition();
// 执行标志位:1-A执行 2-B执行 3-C执行
private int flag = 1;
/**
* A线程打印5次
*/
public void print5() {
lock.lock();
try {
// 判断:不是A线程执行时机则等待
while (flag != 1) {
conditionA.await();
}
// 干活:打印5次
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + "," + i);
}
// 修改标志位,唤醒B线程
flag = 2;
conditionB.signalAll();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
/**
* B线程打印10次
*/
public void print10() {
lock.lock();
try {
while (flag != 2) {
conditionB.await();
}
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + "," + i);
}
flag = 3;
conditionC.signalAll();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
/**
* C线程打印15次
*/
public void print15() {
lock.lock();
try {
while (flag != 3) {
conditionC.await();
}
for (int i = 1; i <= 15; i++) {
System.out.println(Thread.currentThread().getName() + "," + i);
}
// 重置标志位,唤醒A线程开始下一轮
flag = 1;
conditionA.signalAll();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ThreadOrderAccess access = new ThreadOrderAccess();
// 启动A线程
new Thread(() -> {
for (int i = 0; i < 10; i++) {
access.print5();
}
}, "AA").start();
// 启动B线程
new Thread(() -> {
for (int i = 0; i < 10; i++) {
access.print10();
}
}, "BB").start();
// 启动C线程
new Thread(() -> {
for (int i = 0; i < 10; i++) {
access.print15();
}
}, "CC").start();
}
}
四、面试实战:12A34B...5152Z
4.1 需求分析
- 线程 1:打印 1-52 的数字
- 线程 2:打印 A-Z 的字母
- 执行顺序:12A34B...5152Z
4.2 实现代码
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class NumberLetterPrint {
private final Lock lock = new ReentrantLock();
private final Condition numCondition = lock.newCondition();
private final Condition letterCondition = lock.newCondition();
// 控制标志位:true-数字线程执行 false-字母线程执行
private boolean flag = true;
// 数字计数器
private int num = 1;
// 字母计数器
private char letter = 'A';
/**
* 打印数字的方法
*/
public void printNumber() {
lock.lock();
try {
while (num <= 52) {
// 不是数字执行时机则等待
while (!flag) {
numCondition.await();
}
// 打印两个数字
System.out.print(num);
System.out.print(num + 1);
num += 2;
// 切换标志位,唤醒字母线程
flag = false;
letterCondition.signal();
// 数字打印完直接退出,避免等待
if (num > 52) {
break;
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
/**
* 打印字母的方法
*/
public void printLetter() {
lock.lock();
try {
while (letter <= 'Z') {
// 不是字母执行时机则等待
while (flag) {
letterCondition.await();
}
// 打印一个字母
System.out.print(letter);
letter++;
// 切换标志位,唤醒数字线程
flag = true;
numCondition.signal();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
NumberLetterPrint print = new NumberLetterPrint();
// 数字线程
new Thread(print::printNumber, "数字线程").start();
// 字母线程
new Thread(print::printLetter, "字母线程").start();
}
}
五、核心总结
5.1 多线程编程模板
- 线程操作资源类:将共享数据和操作封装到独立的资源类中,高内聚低耦合
- 判断 - 干活 - 通知:在资源类方法中遵循 "判断条件→执行逻辑→通知其他线程" 的流程
- 防止虚假唤醒:使用 while 循环替代 if 判断等待条件
5.2 关键注意事项
- wait/await 必须在同步块中使用:否则会抛出 IllegalMonitorStateException
- 虚假唤醒:多线程场景下必须使用 while 循环重新检查条件
- Condition 优势:一个 Lock 可以创建多个 Condition,实现精准的线程唤醒
- 锁的释放:finally 块中必须释放锁,避免死锁
5.3 技术选型建议
- 简单场景:使用 synchronized + wait/notify
- 复杂场景(需要精准唤醒):使用 Lock + Condition
- 高并发场景:考虑使用 CountDownLatch、CyclicBarrier 等工具类
通过以上案例和分析,我们可以看到线程间通信的核心是通过等待 - 唤醒机制协调线程执行顺序,结合标志位控制可以实现复杂的线程调度需求。在实际开发中,合理选择通信方式并遵循编程模板,能够有效避免多线程问题。