Java中的ReentrantLock与Condition详解
字数 1632 2025-11-20 00:55:52

Java中的ReentrantLock与Condition详解

1. 背景与问题描述

在多线程编程中,synchronized是Java原生的互斥同步机制,但它的功能相对简单(例如无法实现公平锁、等待条件单一)。ReentrantLock是JUC(java.util.concurrent)包下提供的显式锁,支持更灵活的锁操作,例如可中断获取锁、超时获取锁、公平锁等。而Condition则是与ReentrantLock配合使用的等待/通知机制,类似于Object.wait()/notify(),但支持多个等待条件队列。

核心问题

  • ReentrantLock如何实现可重入性?
  • synchronized相比有哪些优势?
  • Condition的工作原理是什么?如何实现多条件等待?

2. ReentrantLock的基本特性

(1)可重入性

  • 定义:同一个线程可以重复获取同一把锁,而不会阻塞自己。
  • 实现原理
    • ReentrantLock内部通过AQS(AbstractQueuedSynchronizer)state字段记录重入次数。
    • 线程首次获取锁时,state从0变为1,并记录锁的持有者(当前线程)。
    • 同一线程再次获取锁时,state递增;释放锁时state递减,直到state=0时完全释放锁。

(2)公平锁 vs 非公平锁

  • 公平锁:线程按申请锁的顺序排队获取锁。
  • 非公平锁:允许插队,可能造成线程饥饿但吞吐量更高。
  • 示例
    ReentrantLock fairLock = new ReentrantLock(true); // 公平锁  
    ReentrantLock nonfairLock = new ReentrantLock();   // 非公平锁(默认)  
    

(3)灵活的方法

  • lock():获取锁,阻塞直到成功。
  • tryLock():尝试非阻塞获取锁,立即返回是否成功。
  • lockInterruptibly():可响应中断的获取锁方式。
  • unlock():释放锁(必须成对调用,通常在finally块中释放)。

3. Condition的等待/通知机制

(1)与Object监视器方法的对比

  • synchronized中,一个锁对象只能有一个等待队列(通过wait()/notify()操作)。
  • ReentrantLock可以绑定多个Condition,每个Condition维护独立的等待队列。

(2)核心方法

  • await():释放锁并进入等待状态(类比wait())。
  • signal():唤醒一个等待线程(类比notify())。
  • signalAll():唤醒所有等待线程(类比notifyAll())。

(3)典型应用场景:生产者-消费者模型

ReentrantLock lock = new ReentrantLock();  
Condition notFull = lock.newCondition();  // 条件1:队列未满  
Condition notEmpty = lock.newCondition(); // 条件2:队列非空  

// 生产者  
public void put(Object item) throws InterruptedException {  
    lock.lock();  
    try {  
        while (queue.isFull()) {  
            notFull.await(); // 等待"未满"条件  
        }  
        queue.add(item);  
        notEmpty.signal(); // 唤醒消费者  
    } finally {  
        lock.unlock();  
    }  
}  

// 消费者  
public Object take() throws InterruptedException {  
    lock.lock();  
    try {  
        while (queue.isEmpty()) {  
            notEmpty.await(); // 等待"非空"条件  
        }  
        Object item = queue.remove();  
        notFull.signal(); // 唤醒生产者  
        return item;  
    } finally {  
        lock.unlock();  
    }  
}  

4. 底层原理:AQS与条件队列

(1)AQS的同步队列

  • ReentrantLock的锁竞争通过AQS的同步队列(CLH队列)管理。
  • 线程获取锁失败时,会被封装成Node节点加入队列尾部,通过自旋或阻塞等待唤醒。

(2)Condition的条件队列

  • 每个Condition对象维护一个独立的条件队列(单向链表)。
  • 调用await()时,线程会释放锁,并将自身从AQS同步队列移到Condition的条件队列中等待。
  • 调用signal()时,将条件队列中的头节点移回AQS同步队列,参与锁竞争。

(3)流程示意图

同步队列(AQS): [ThreadA] <- [ThreadB] <- [ThreadC]  
条件队列(Condition): [ThreadX] <- [ThreadY]  

ThreadX调用await():  
1. ThreadX从同步队列移出,加入条件队列。  
2. ThreadB成为同步队列头节点,获取锁。  

ThreadB调用signal():  
1. 将ThreadX从条件队列移回同步队列尾部。  
2. ThreadX在同步队列中等待锁。  

5. 对比synchronized的优劣

优势:

  1. 功能丰富:支持公平锁、可中断、超时尝试等。
  2. 性能优化:在竞争激烈时,非公平锁的吞吐量更高。
  3. 多条件等待:通过多个Condition精确控制线程唤醒。

劣势:

  1. 需手动释放锁:忘记调用unlock()会导致死锁。
  2. 代码复杂:需配合try-finally块确保锁释放。

6. 总结

  • ReentrantLock通过AQS实现可重入锁,支持公平/非公平策略。
  • Condition将等待/通知机制解耦,允许一个锁绑定多个条件队列。
  • 适用场景:需要高级功能(如公平性、多条件)的并发控制,但需注意手动管理锁的释放。
Java中的ReentrantLock与Condition详解 1. 背景与问题描述 在多线程编程中, synchronized 是Java原生的互斥同步机制,但它的功能相对简单(例如无法实现公平锁、等待条件单一)。 ReentrantLock 是JUC( java.util.concurrent )包下提供的显式锁,支持更灵活的锁操作,例如可中断获取锁、超时获取锁、公平锁等。而 Condition 则是与 ReentrantLock 配合使用的等待/通知机制,类似于 Object.wait() / notify() ,但支持多个等待条件队列。 核心问题 : ReentrantLock 如何实现可重入性? 与 synchronized 相比有哪些优势? Condition 的工作原理是什么?如何实现多条件等待? 2. ReentrantLock的基本特性 (1)可重入性 定义 :同一个线程可以重复获取同一把锁,而不会阻塞自己。 实现原理 : ReentrantLock 内部通过 AQS(AbstractQueuedSynchronizer) 的 state 字段记录重入次数。 线程首次获取锁时, state 从0变为1,并记录锁的持有者(当前线程)。 同一线程再次获取锁时, state 递增;释放锁时 state 递减,直到 state=0 时完全释放锁。 (2)公平锁 vs 非公平锁 公平锁 :线程按申请锁的顺序排队获取锁。 非公平锁 :允许插队,可能造成线程饥饿但吞吐量更高。 示例 : (3)灵活的方法 lock() :获取锁,阻塞直到成功。 tryLock() :尝试非阻塞获取锁,立即返回是否成功。 lockInterruptibly() :可响应中断的获取锁方式。 unlock() :释放锁(必须成对调用,通常在 finally 块中释放)。 3. Condition的等待/通知机制 (1)与Object监视器方法的对比 synchronized 中,一个锁对象只能有一个等待队列(通过 wait() / notify() 操作)。 ReentrantLock 可以绑定多个 Condition ,每个 Condition 维护独立的等待队列。 (2)核心方法 await() :释放锁并进入等待状态(类比 wait() )。 signal() :唤醒一个等待线程(类比 notify() )。 signalAll() :唤醒所有等待线程(类比 notifyAll() )。 (3)典型应用场景:生产者-消费者模型 4. 底层原理:AQS与条件队列 (1)AQS的同步队列 ReentrantLock 的锁竞争通过AQS的 同步队列 (CLH队列)管理。 线程获取锁失败时,会被封装成Node节点加入队列尾部,通过自旋或阻塞等待唤醒。 (2)Condition的条件队列 每个 Condition 对象维护一个独立的 条件队列 (单向链表)。 调用 await() 时,线程会释放锁,并将自身从AQS同步队列移到Condition的条件队列中等待。 调用 signal() 时,将条件队列中的头节点移回AQS同步队列,参与锁竞争。 (3)流程示意图 5. 对比synchronized的优劣 优势: 功能丰富 :支持公平锁、可中断、超时尝试等。 性能优化 :在竞争激烈时,非公平锁的吞吐量更高。 多条件等待 :通过多个 Condition 精确控制线程唤醒。 劣势: 需手动释放锁 :忘记调用 unlock() 会导致死锁。 代码复杂 :需配合 try-finally 块确保锁释放。 6. 总结 ReentrantLock 通过AQS实现可重入锁,支持公平/非公平策略。 Condition 将等待/通知机制解耦,允许一个锁绑定多个条件队列。 适用场景:需要高级功能(如公平性、多条件)的并发控制,但需注意手动管理锁的释放。