请添加图片描述

👋 欢迎阅读《Java面试200问》系列博客!

🚀大家好,我是Jinkxs,一名热爱Java、深耕技术一线的开发者。在准备和参与了数十场Java面试后,我深知面试不仅是对知识的考察,更是对理解深度与表达能力的综合检验。

✨本系列将带你系统梳理Java核心技术中的高频面试题,从源码原理到实际应用,从常见陷阱到大厂真题,每一篇文章都力求深入浅出、图文并茂,帮助你在求职路上少走弯路,稳拿Offer!

🔍今天我们要聊的是:《Condition 接口与 await()/signal() 方法》。准备好了吗?Let’s go!


🧰 Condition 接口与 await()/signal() 方法:Java 并发中的“信号灯大师”

“如果说 synchronized 是原始的‘打手势’,那 Condition 就是现代交通中的‘红绿灯系统’——精准、可控、多路并行!”

欢迎阅读《Condition 接口与 await/signal 方法的深度解析》—— 专为 Java 高并发面试打造的“线程通信指南”。我们将用生动的比喻丰富的代码示例直观的图表深度源码剖析,带你彻底掌握 Condition 如何优雅地实现线程间的等待与唤醒。

本文目录

  1. 🚦 前言:为什么我们需要“信号灯”?
  2. 🔧 Condition 接口基础
  3. 🔄 await() 与 signal() 方法详解
  4. 🏭 生产者-消费者模型实战
  5. 🚦 多条件变量:交通灯的“左转/直行/右转”
  6. 🔍 源码探秘:AQS 中的 ConditionObject
  7. ⚡ 与 Object 的 wait/notify 对比
  8. 🔥 面试篇:“信号灯连环问”
  9. 🛠️ 最佳实践:何时用 Condition?
  10. 🔚 终极总结:做一名智慧的“信号灯指挥官”

1. 前言:为什么我们需要“信号灯”?

🚶‍♂️ 场景一:原始社会“打手势”(Object 的 wait/notify)

  • 规则:一个人喊“等会儿!”,另一个人喊“好了!”,但谁喊谁听,全靠默契。
  • 缺点:混乱、不可控、容易误唤醒。

🚦 场景二:现代交通“红绿灯”(Condition)

  • 规则:多个方向有独立的红绿灯,左转、直行、右转各走各的。
  • 优点:精准、高效、可扩展。

在 Java 并发中:

  • Object.wait() / notify() → 原始“打手势”
  • Condition.await() / signal() → 现代“红绿灯”

一句话定义

Conditionjava.util.concurrent.locks 包下的接口,它允许线程在某个条件下挂起(await),并在另一个线程中唤醒(signal),Lock 配合使用,提供比 synchronized 更灵活的线程通信机制


2. Condition 接口基础

📦 获取 Condition 实例

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ConditionDemo {
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition(); // 通过 Lock 创建 Condition
}

关键:一个 Lock 可以创建多个 Condition 实例,实现多路通知。


📊 Condition 接口主要方法

方法 描述
void await() 当前线程进入等待状态,释放锁,直到被 signal 或中断
void signal() 唤醒一个等待中的线程
void signalAll() 唤醒所有等待中的线程
long awaitNanos(long nanosTimeout) 超时等待(纳秒)
boolean awaitUntil(Date deadline) 等待到指定时间
boolean await(long time, TimeUnit unit) 超时等待(指定单位)

3. await() 与 signal() 方法详解

🔄 await() 方法流程

        ┌─────────────────────┐
        │ 调用 await()         │
        └─────────────────────┘
                   │
         ┌─────────▼─────────┐
         │ 释放当前持有的锁     │
         └─────────┬─────────┘
                   │
         ┌─────────▼─────────┐
         │ 当前线程进入 Condition │
         │ 等待队列(阻塞)      │
         └─────────┬─────────┘
                   │
         ┌─────────▼─────────┐
         │ 被 signal() 唤醒     │
         └─────────┬─────────┘
                   │
         ┌─────────▼─────────┐
         │ 重新尝试获取锁       │
         └─────────┬─────────┘
                   │
         ┌─────────▼─────────┐
         │ 获取锁成功,继续执行   │
         └─────────────────────┘

🔄 signal() 方法流程

        ┌─────────────────────┐
        │ 调用 signal()        │
        └─────────────────────┘
                   │
         ┌─────────▼─────────┐
         │ 从 Condition 等待队列 │
         │ 中唤醒一个线程       │
         └─────────┬─────────┘
                   │
         ┌─────────▼─────────┐
         │ 被唤醒的线程尝试重新   │
         │ 获取锁(此时仍持有锁)│
         └─────────┬─────────┘
                   │
         ┌─────────▼─────────┐
         │ signal() 返回,继续执行 │
         └─────────────────────┘

🧪 代码演示:基础 await/signal

public class BasicConditionDemo {
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();
    private boolean isReady = false;

    public void waitForReady() throws InterruptedException {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " 等待准备就绪...");
            while (!isReady) {
                condition.await(); // 释放锁,进入等待
            }
            System.out.println(Thread.currentThread().getName() + " 被唤醒,继续执行!");
        } finally {
            lock.unlock();
        }
    }

    public void setReady() {
        lock.lock();
        try {
            System.out.println("准备就绪,发出信号!");
            isReady = true;
            condition.signal(); // 唤醒一个等待线程
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        BasicConditionDemo demo = new BasicConditionDemo();

        Thread waiter = new Thread(demo::waitForReady, "waiter-thread");
        waiter.start();

        Thread.sleep(1000);

        Thread setter = new Thread(demo::setReady, "setter-thread");
        setter.start();

        waiter.join();
        setter.join();
    }
}

📊 输出

waiter-thread 等待准备就绪...
准备就绪,发出信号!
waiter-thread 被唤醒,继续执行!

关键点

  • await()释放锁,否则 setter 线程无法获取锁来调用 setReady()
  • 使用 while 而不是 if 检查条件,防止虚假唤醒

4. 生产者-消费者模型实战

🏭 经典问题

  • 生产者生产数据,放入缓冲区。
  • 消费者从缓冲区取数据。
  • 缓冲区满时,生产者等待。
  • 缓冲区空时,消费者等待。

🧪 代码实现

import java.util.LinkedList;
import java.util.Queue;

public class ProducerConsumer {
    private final Queue<Integer> buffer = new LinkedList<>();
    private final int MAX_SIZE = 5;

    private final ReentrantLock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition(); // 缓冲区不满
    private final Condition notEmpty = lock.newCondition(); // 缓冲区不空

    public void produce(int item) throws InterruptedException {
        lock.lock();
        try {
            while (buffer.size() == MAX_SIZE) {
                System.out.println("缓冲区满,生产者等待...");
                notFull.await();
            }
            buffer.offer(item);
            System.out.println("生产者生产: " + item);
            notEmpty.signal(); // 唤醒消费者
        } finally {
            lock.unlock();
        }
    }

    public int consume() throws InterruptedException {
        lock.lock();
        try {
            while (buffer.isEmpty()) {
                System.out.println("缓冲区空,消费者等待...");
                notEmpty.await();
            }
            int item = buffer.poll();
            System.out.println("消费者消费: " + item);
            notFull.signal(); // 唤醒生产者
            return item;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        ProducerConsumer pc = new ProducerConsumer();

        Thread producer = new Thread(() -> {
            try {
                for (int i = 1; i <= 10; i++) {
                    pc.produce(i);
                    Thread.sleep(500);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "Producer");

        Thread consumer = new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    pc.consume();
                    Thread.sleep(800);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "Consumer");

        producer.start();
        consumer.start();
    }
}

📊 输出(节选)

生产者生产: 1
消费者消费: 1
生产者生产: 2
生产者生产: 3
消费者消费: 2
消费者消费: 3
...
缓冲区空,消费者等待...
生产者生产: 9
消费者消费: 9
生产者生产: 10
消费者消费: 10

关键:使用两个 Condition 实现精准唤醒,避免了 notifyAll() 的“惊群效应”。


5. 多条件变量:交通灯的“左转/直行/右转”

🚦 一个 Lock,多个 Condition

public class TrafficLight {
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition leftTurn = lock.newCondition();  // 左转车道
    private final Condition straight = lock.newCondition();  // 直行车道
    private final Condition rightTurn = lock.newCondition(); // 右转车道

    public void waitForLeftTurn() throws InterruptedException {
        lock.lock();
        try {
            System.out.println("左转车辆等待...");
            leftTurn.await();
            System.out.println("左转车辆通过!");
        } finally {
            lock.unlock();
        }
    }

    public void allowLeftTurn() {
        lock.lock();
        try {
            System.out.println("绿灯:左转通行!");
            leftTurn.signal();
        } finally {
            lock.unlock();
        }
    }

    // 其他车道类似...
}

💡 优势

  • 精准控制:只唤醒特定条件的线程。
  • 避免惊群signal() 只唤醒一个,signalAll() 只唤醒对应队列的线程。
  • 可扩展性:可定义任意多个条件。

6. 源码探秘:AQS 中的 ConditionObject

🔍 ConditionObject 内部结构

Condition 的实现类是 AbstractQueuedSynchronizer.ConditionObject

它内部维护了一个单向链表(等待队列),与 AQS 的双向同步队列分离。

AQS 同步队列(FIFO):
Head <-> Node1 <-> Node2 <-> ... <-> Tail
          ▲
          │
Condition 等待队列(单向):
FirstWaiter -> NodeA -> NodeB -> ... -> LastWaiter

🔍 await() 源码关键步骤

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    // 1. 将当前线程包装成 Node,加入 Condition 等待队列
    Node node = addConditionWaiter();
    // 2. 完全释放锁(可能可重入多次)
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    // 3. 检查线程是否在同步队列中(被 signal 后会转移)
    while (!isOnSyncQueue(node)) {
        // 4. 阻塞线程
        LockSupport.park(this);
        // 处理中断...
    }
    // 5. 被唤醒后,重新竞争锁
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    // 清理取消的节点...
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

🔍 signal() 源码关键步骤

public final void signal() {
    // 必须持有锁
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        // 唤醒第一个等待节点
        doSignal(first);
}

private void doSignal(Node first) {
    do {
        if ((firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

// 将节点从 Condition 队列转移到 AQS 同步队列
final boolean transferForSignal(Node node) {
    // 将节点状态改为 0(等待被唤醒)
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;
    // 加入 AQS 同步队列
    Node p = enq(node);
    int ws = p.waitStatus;
    // 唤醒线程,使其进入 AQS 的 acquire 流程
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

关键signal() 并不立即唤醒线程,而是将其转移到 AQS 同步队列,等待 signal() 方法的调用者释放锁后,该线程才能竞争锁。


7. 与 Object 的 wait/notify 对比

📊 表格:Condition vs Object 通信机制

特性 Condition Object (wait/notify)
所属 java.util.concurrent.locks java.lang.Object
依赖 必须与 Lock 配合 必须与 synchronized 配合
多条件 ✅ 支持多个 Condition ❌ 只有一个等待队列
唤醒精度 高(可指定唤醒) 低(notify 随机,notifyAll 全部)
中断响应 更好(await 可中断) 一般(wait 可中断)
超时控制 更丰富(多种方法) 基本(wait(long)
灵活性 ⭐⭐⭐⭐⭐ ⭐⭐⭐

🧪 代码对比:生产者-消费者

❌ 使用 wait/notify(低效)
synchronized (this) {
    while (buffer.isEmpty()) {
        wait(); // 可能唤醒生产者或消费者
    }
    // 消费...
    notifyAll(); // 必须 notifyAll,否则可能死锁
}
✅ 使用 Condition(高效)
lock.lock();
try {
    while (buffer.isEmpty()) {
        notEmpty.await(); // 只等待 notEmpty
    }
    // 消费...
    notFull.signal(); // 只唤醒生产者
} finally {
    lock.unlock();
}

结论Condition 提供了更细粒度的控制,避免了不必要的唤醒和竞争。


8. 面试篇:“信号灯连环问”

🤔 面试题 1:await() 为什么必须在 lock()unlock() 之间调用?

  • await() 需要释放当前持有的锁,否则其他线程无法获取锁来修改条件或调用 signal()
  • 这与 wait() 必须在 synchronized 块中调用同理。

🤔 面试题 2:signal() 后,被唤醒的线程会立即执行吗?

  • 不会
  • signal() 只是将线程从 Condition 队列转移到 AQS 同步队列。
  • 被唤醒的线程必须等到 signal() 的调用者释放锁后,才能竞争锁并继续执行。

🤔 面试题 3:signal()signalAll() 的区别?何时用哪个?

  • signal():唤醒一个等待线程(通常是队列头部)。
  • signalAll():唤醒所有等待线程。
  • 何时用
    • 如果条件满足后,只有一个线程能继续执行(如生产者-消费者中的 notFull),用 signal()
    • 如果多个线程都可能满足条件,用 signalAll()

🤔 面试题 4:如何避免“虚假唤醒”?

  • 使用 while 循环检查条件,而不是 if
  • 因为即使没有收到 signal,线程也可能被意外唤醒(如系统信号)。
while (!condition) {
    condition.await();
}

🤔 面试题 5:Condition 的等待队列和 AQS 的同步队列有什么关系?

  • 等待队列(Condition Queue):单向链表,存放因 await() 而阻塞的线程。
  • 同步队列(Sync Queue):双向链表,存放竞争锁的线程。
  • 当线程调用 await() 时,从同步队列转移到等待队列。
  • 当调用 signal() 时,从等待队列转移到同步队列,重新参与锁竞争。

9. 最佳实践:何时用 Condition?

✅ 推荐使用 Condition 的场景

  • 需要多路通知的场景(如生产者-消费者)
  • 高并发、高性能要求的系统
  • 需要精确唤醒,避免“惊群效应”
  • 使用 ReentrantLock 作为锁机制
private final ReentrantLock lock = new ReentrantLock();
private final Condition conditionA = lock.newCondition();
private final Condition conditionB = lock.newCondition();

❌ 不推荐使用 Condition 的场景

  • 简单的线程协作,synchronized + wait/notify 足够
  • 代码复杂度要求极低的场景

10. 终极总结:做一名智慧的“信号灯指挥官”

经过这场“信号灯系统”的深度探索,你已经掌握了 Condition 如何优雅地实现线程通信。

记忆口诀

await 释放锁,进队列,等信号。

signal 唤醒它,转队列,争锁去。

多条件,精准控,效率高,推荐用!

决策树

                            ┌─────────────────────┐
                            │ 是否使用 ReentrantLock?│
                            └─────────────────────┘
                                       │
                       ┌───────────────┴───────────────┐
                       ▼                               ▼
         ┌──────────────────────────────┐    ┌──────────────────────────────┐
         │ 需要多条件或精确唤醒?       │    │ 用 synchronized + wait/notify │
         └────────────────────┬───────┘    └──────────────────────────────┘
                            ▼
         ┌──────────────────────────────┐
         │ 使用 Condition               │
         │ - 创建多个 Condition 实例    │
         │ - await()/signal() 精准控制  │
         └──────────────────────────────┘

下次在设计并发程序时,请像一位真正的“信号灯指挥官”一样,根据业务需求选择合适的“通信机制”。记住:

真正的并发高手,不在于用最复杂的锁,而在于用最精准的“信号”,指挥最混乱的“车流”。

🚦 灯亮车行,灯灭车停,秩序井然,系统畅通!

🎯 总结一下:

本文深入探讨了《Condition 接口与 await()/signal() 方法》,从原理到实践,解析了面试中常见的考察点和易错陷阱。掌握这些内容,不仅能应对面试官的连环追问,更能提升你在实际开发中的技术判断力。

🔗 下期预告:我们将继续深入Java面试核心,带你解锁《ReadWriteLock 读写锁的实现与应用场景》 的关键知识点,记得关注不迷路!

💬 互动时间:你在面试中遇到过类似问题吗?或者对本文内容有疑问?欢迎在评论区留言交流,我会一一回复!

如果你觉得这篇文章对你有帮助,别忘了 点赞 + 收藏 + 转发,让更多小伙伴一起进步!我们下一篇见 👋

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐