Java中关于多线程 ReentrantLock + Condition 的代码理解,逐步拆解,一文弄懂!

ReentrantLock + Condition 的代码理解

源于:https://liaoxuefeng.com/books/java/threading/synchronize/condition/index.html

源代码如下:

class TaskQueue {
    private final Lock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();
    private Queue<String> queue = new LinkedList<>();

    public void addTask(String s) {
        lock.lock();
        try {
            queue.add(s);
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public String getTask() {
        lock.lock();
        try {
            while (queue.isEmpty()) {
                condition.await();
            }
            return queue.remove();
        } finally {
            lock.unlock();
        }
    }
}

使用 ReentrantLock + Condition 实现线程安全的任务队列

这段代码使用了 ReentrantLock + Condition 来实现一个线程安全的任务队列,解决了之前 synchronized + while空转 导致 CPU 100% 占用的问题。


1. 成员变量

private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private Queue<String> queue = new LinkedList<>();
  • lock:显式锁,用于代替 synchronized 来控制并发。
  • condition:条件对象,相当于 Object.wait() / notify() 的替代品,但功能更强。
  • queue:任务队列。

2. addTask(String s)

public void addTask(String s) {
    lock.lock();
    try {
        queue.add(s);
        condition.signalAll();
    } finally {
        lock.unlock();
    }
}
  • lock.lock() 加锁,确保线程安全。
  • 往 queue 添加任务
  • condition.signalAll():通知所有在 condition.await() 上等待的线程,“有新任务了,可以继续执行”。
  • finally 里 unlock(),防止死锁

3. getTask()

public String getTask() {
    lock.lock();
    try {
        while (queue.isEmpty()) {
            condition.await();
        }
        return queue.remove();
    } finally {
        lock.unlock();
    }
}
  • lock.lock():加锁,防止多个线程同时修改队列。
  • while (queue.isEmpty()) { condition.await(); }
    • 如果队列空,则进入等待。
    • condition.await() 会做两件事
      1. 释放 lock,让 addTask() 可以拿到锁执行。
      2. 当前线程阻塞(不会空转消耗CPU)
    • addTask() 调用 condition.signalAll(),等待的线程会被唤醒,重新竞争锁。
  • return queue.remove();:取出并返回任务。

核心改进点

  • 之前的写法:while (queue.isEmpty()) {} 死循环,占用 CPU 100%,因为线程在不停地做条件检查。
  • 现在的写法:condition.await() 让线程挂起,不消耗CPU,直到被 signalAll() 唤醒。

为什么要 while 而不是 if?

因为可能出现 虚假唤醒(spurious wakeup),线程被唤醒后,队列可能仍然是空的,所以需要再次检查 queue.isEmpty()


符号简笔图:运行流程

符号约定

  • 🔒 锁定:线程获取 Lock
  • 等待:线程挂起,等待条件 signal
  • 唤醒condition.signalAll()
  • 📦 任务队列:LinkedList

步骤图

初始状态

[线程 A]  ---->  getTask()
[线程 B]  ---->  addTask()
任务队列: [  ]  (空)
Lock: 🔓 (未锁)

线程 A 调用 getTask()

[线程 A]  获取 Lock 🔒
队列为空 -> 进入 while 循环
调用 condition.await()
↓
释放 Lock 🔓,挂起 ⏳,等待 signal

当前状态:

[线程 A]   (等待)
任务队列: [  ]  (空)
Lock: 🔓

线程 B 调用 addTask("X")

[线程 B]  获取 Lock 🔒
queue.add("X")  -> 任务队列:[X]
调用 condition.signalAll() ✅ (通知等待线程)
释放 Lock 🔓

当前状态:

任务队列:[ X ]
[线程 A] (被唤醒)

线程 A 被唤醒

[线程 A] 重新竞争 Lock 🔒
条件满足 (队列不为空)
queue.remove() -> 获取 "X"
释放 Lock 🔓
返回 "X"

简图总结

getTask():
[获取锁🔒] -> [队列为空?] -> [await() ⏳ 等待 signal]
                  ↑
                  |
addTask(): [获取锁🔒] -> [添加任务 📦] -> [signalAll ✅ 唤醒等待线程]

关键点直观解释

  • await() 会释放锁并挂起,防止死锁。
  • signalAll() 唤醒所有等待线程,让它们重新尝试获取锁。
  • 任务队列的读写都必须在 lock 下完成,保证线程安全。
posted @ 2025-08-30 21:09  AlphaGeek  阅读(6)  评论(0)    收藏  举报