在多线程编程中线程间通信的核心是“协同”——让线程在合适的时机等待、在条件满足时唤醒从而实现有序执行。我们之前已经掌握了synchronized wait()/notify()的基础通信方式但它存在一个明显的短板无法精准唤醒指定类型的线程容易造成不必要的线程切换降低并发效率。而Lock Condition的组合正是为解决这个痛点而生。Condition 是 Java 并发包中与 Lock 配套的线程通信工具它能实现“精准唤醒”让线程间的协同更灵活、更高效也是面试中高频考察的进阶知识点。今天我们就彻底拆解 Condition 实现线程通信的核心从它的本质、与 wait/notify 的区别到实战案例结合你熟悉的“加减1”场景、生产者消费者模型再到避坑要点和面试考点帮你从“会用”到“吃透”轻松掌握这个并发编程的进阶技巧。一、Condition 是什么核心作用是什么Condition 直译是“条件”它是java.util.concurrent.locks包下的接口必须与 Lock 锁配合使用核心作用是为线程提供一个“等待/唤醒”的条件机制让线程在某个条件不满足时阻塞等待在条件满足时被唤醒实现线程间的精准协同。简单类比如果把 Lock 看作“一把锁”那么 Condition 就相当于“锁上的多个等待队列”——每个 Condition 对应一个等待队列线程可以根据不同的条件进入不同的队列等待唤醒时也能精准唤醒某个队列中的线程而不是唤醒所有等待线程。1.1 Condition 的核心关联与 Lock 绑定Condition 不能单独使用必须通过Lock.newCondition()方法创建这意味着一个 Lock 可以创建多个 Condition 对象每个 Condition 对应一个独立的等待队列。举个基础示例快速入门import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ConditionDemo { // 1. 创建Lock锁常用ReentrantLock private final Lock lock new ReentrantLock(); // 2. 通过Lock创建Condition对象可创建多个 private final Condition condition lock.newCondition(); // 线程等待方法 public void waitMethod() throws InterruptedException { lock.lock(); // 必须先获取锁 try { // 条件不满足时线程进入Condition的等待队列释放锁 condition.await(); } finally { lock.unlock(); // 确保锁释放避免死锁 } } // 线程唤醒方法 public void signalMethod() { lock.lock(); try { // 唤醒Condition等待队列中的一个线程 condition.signal(); } finally { lock.unlock(); } } }1.2 Condition 的核心方法必记Condition 接口提供了与 wait()/notify() 对应的方法功能类似但更灵活核心方法有3个await()类似wait()线程进入等待状态释放当前持有的 Lock 锁直到被signal()/signalAll()唤醒或被中断signal()类似notify()唤醒 Condition 等待队列中的一个线程该线程会重新竞争 Lock 锁signalAll()类似notifyAll()唤醒 Condition 等待队列中的所有线程这些线程会竞争 Lock 锁。补充Condition 还有await(long time, TimeUnit unit)等重载方法支持超时等待避免线程无限阻塞实战中非常实用。二、核心对比Condition vs wait()/notify()为什么选Condition我们之前用synchronized wait()/notify()实现过线程通信比如你的加减1场景但它有两个难以解决的问题而 Condition 恰好能完美破解。下面通过对比清晰看到 Condition 的优势特性synchronized wait()/notify()Lock Condition等待队列数量只有1个等待队列所有等待线程都在同一个队列中多个等待队列一个Condition对应一个队列可按条件分组唤醒方式只能唤醒所有线程notifyAll()或随机唤醒一个notify()无法精准唤醒可精准唤醒指定队列的线程signal()/signalAll()减少线程切换灵活性低无法自定义等待/唤醒逻辑不支持超时、中断的精细控制高支持超时等待、中断响应可创建多个Condition实现复杂协同锁释放wait() 自动释放锁notify()/notifyAll() 不释放锁await() 自动释放锁signal()/signalAll() 不释放锁需手动解锁适用场景简单线程通信场景如两个线程交替执行复杂线程协同场景如多生产者多消费者、按条件唤醒一句话总结优势Condition 解决了 wait()/notify()“唤醒不精准”的痛点让线程间的协同更灵活、更高效是复杂并发场景的首选。三、实战案例1用Condition实现“加减1”交替执行贴合你的代码我们先基于你熟悉的“初始值为0一个线程加1、一个线程减1交替执行10轮”的场景用 Lock Condition 实现对比之前的 synchronized 版本感受 Condition 的精准性。3.1 完整实现代码import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; // 资源类用LockCondition实现线程通信 class ShareData { private Integer number 0; // 1. 创建可重入锁 private final Lock lock new ReentrantLock(); // 2. 创建两个Condition分别对应“可以加1”和“可以减1”的条件 private final Condition addCondition lock.newCondition(); // 加1线程的等待队列 private final Condition subCondition lock.newCondition(); // 减1线程的等待队列 /** * 加1方法只有number为0时才能执行 */ public void increment() throws InterruptedException { lock.lock(); // 先获取锁 try { // 核心用while判断条件避免虚假唤醒和之前讲的一致 while (number ! 0) { // 条件不满足加1线程进入addCondition队列等待 addCondition.await(); } // 条件满足执行加1操作 number; System.out.println(Thread.currentThread().getName() : number); // 加1完成后唤醒减1线程精准唤醒subCondition队列 subCondition.signal(); } finally { lock.unlock(); // 确保锁释放 } } /** * 减1方法只有number为1时才能执行 */ public void decrement() throws InterruptedException { lock.lock(); try { // 用while判断条件避免虚假唤醒 while (number ! 1) { // 条件不满足减1线程进入subCondition队列等待 subCondition.await(); } // 条件满足执行减1操作 number--; System.out.println(Thread.currentThread().getName() : number); // 减1完成后唤醒加1线程精准唤醒addCondition队列 addCondition.signal(); } finally { lock.unlock(); } } } // 测试类 public class ConditionAddSubDemo { public static void main(String[] args) { ShareData shareData new ShareData(); // 加1线程执行10轮 new Thread(() - { for (int i 0; i 10; i) { try { shareData.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }, AAA).start(); // 减1线程执行10轮 new Thread(() - { for (int i 0; i 10; i) { try { shareData.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, BBB).start(); } }3.2 核心逻辑拆解重点这个案例的关键的是“精准唤醒”对比你之前的 synchronized 版本优势非常明显创建两个 ConditionaddCondition加1线程的等待队列和subCondition减1线程的等待队列实现线程按“加1/减1”分组等待加1线程执行完后只唤醒subCondition队列中的减1线程不会唤醒其他无关线程比如另一个加1线程减1线程执行完后只唤醒addCondition队列中的加1线程实现“加1→减1→加1”的完美交替依然用while判断条件避免虚假唤醒这是无论哪种通信方式都必须遵守的原则。运行结果会和你之前的代码一致但底层的线程调度更高效——没有不必要的线程唤醒减少了锁竞争的开销。四、实战案例2用Condition实现多生产者多消费者模型进阶Condition 的真正优势在多生产者、多消费者场景中体现得淋漓尽致。比如2个生产者线程生产数据3个消费者线程消费数据缓冲区容量为3用 Condition 实现精准协同避免唤醒无关线程。4.1 完整实现代码import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; // 缓冲区资源类用LockCondition实现多生产者多消费者 class Buffer { private final QueueInteger queue new LinkedList(); private final int capacity 3; // 缓冲区容量 private final Lock lock new ReentrantLock(); // 两个Condition分别对应“缓冲区不满”和“缓冲区不空” private final Condition notFull lock.newCondition(); // 生产者等待队列缓冲区满时等待 private final Condition notEmpty lock.newCondition(); // 消费者等待队列缓冲区空时等待 // 生产数据生产者调用 public void produce(int data) throws InterruptedException { lock.lock(); try { // 缓冲区满生产者进入notFull队列等待 while (queue.size() capacity) { System.out.println(缓冲区已满生产者[ Thread.currentThread().getName() ]等待); notFull.await(); } // 生产数据 queue.offer(data); System.out.println(生产者[ Thread.currentThread().getName() ]生产 data 当前缓冲区 queue.size()); // 唤醒消费者缓冲区不空了精准唤醒notEmpty队列 notEmpty.signalAll(); } finally { lock.unlock(); } } // 消费数据消费者调用 public void consume() throws InterruptedException { lock.lock(); try { // 缓冲区空消费者进入notEmpty队列等待 while (queue.isEmpty()) { System.out.println(缓冲区为空消费者[ Thread.currentThread().getName() ]等待); notEmpty.await(); } // 消费数据 int data queue.poll(); System.out.println(消费者[ Thread.currentThread().getName() ]消费 data 当前缓冲区 queue.size()); // 唤醒生产者缓冲区不满了精准唤醒notFull队列 notFull.signalAll(); } finally { lock.unlock(); } } } // 测试类 public class ConditionProducerConsumerDemo { public static void main(String[] args) { Buffer buffer new Buffer(); // 2个生产者线程每个生产5条数据 for (int i 1; i 2; i) { int producerId i; new Thread(() - { for (int j 1; j 5; j) { try { buffer.produce(producerId * 10 j); Thread.sleep(500); // 模拟生产耗时 } catch (InterruptedException e) { e.printStackTrace(); } } }, 生产者 i).start(); } // 3个消费者线程每个消费3条数据 for (int i 1; i 3; i) { new Thread(() - { for (int j 1; j 3; j) { try { buffer.consume(); Thread.sleep(1000); // 模拟消费耗时 } catch (InterruptedException e) { e.printStackTrace(); } } }, 消费者 i).start(); } } }4.2 核心优势解析这个案例中Condition 的精准唤醒优势被最大化生产者只在“缓冲区满”时等待进入 notFull 队列消费完成后只唤醒 notFull 队列中的生产者不会唤醒消费者消费者只在“缓冲区空”时等待进入 notEmpty 队列生产完成后只唤醒 notEmpty 队列中的消费者不会唤醒生产者相比 synchronized notifyAll()唤醒所有线程包括无关的生产者/消费者Condition 减少了大量不必要的线程切换提升了并发效率。五、Condition 实战避坑要点必看Condition 虽然灵活但使用时如果不注意细节很容易出现死锁、数据错乱等问题结合你之前的踩坑经历整理4个核心避坑要点避坑1必须先获取 Lock 锁才能调用 await()/signal()/signalAll()这是最基础也是最容易踩的坑如果没有获取 Lock 锁直接调用 Condition 的方法会抛出IllegalMonitorStateException异常。正确逻辑lock.lock() → 调用Condition方法 → lock.unlock()且 await()/signal() 必须放在 try 块中确保锁能正常释放。避坑2用 while 循环判断条件避免虚假唤醒和 synchronized wait() 一样Condition 的 await() 也可能发生虚假唤醒因此必须用 while 循环判断条件而非 if。错误写法if (queue.isEmpty()) { notEmpty.await(); }虚假唤醒后条件可能已变化导致逻辑错误正确写法while (queue.isEmpty()) { notEmpty.await(); }唤醒后再次检查条件不满足则继续等待。避坑3signal()/signalAll() 不会释放锁需手动解锁很多开发者会误以为“调用 signal() 后会自动释放锁”其实不然——signal() 只是唤醒等待线程锁依然被当前线程持有必须手动调用lock.unlock()释放锁被唤醒的线程才能竞争到锁并执行。如果忘记解锁会导致所有等待线程一直阻塞最终程序卡死。避坑4一个 Lock 可以创建多个 Condition但 Condition 不能跨 Lock 使用Condition 是与 Lock 绑定的通过哪个 Lock 创建的 Condition就只能在该 Lock 的锁范围内使用。如果用 LockA 创建的 Condition在 LockB 的锁范围内调用 await()会抛出异常。