Skip to content

Locks-ReentrantLock源码解析

1. ReentrantLock简介

ReentrantLock是Java并发包(java.util.concurrent.locks)中的一个可重入互斥锁。它具有与使用synchronized方法和语句访问的隐式监视器锁相同的基本行为和语义,但扩展了一些高级功能,如可轮询的锁请求、定时的锁请求、可中断的锁请求等。它还具有更好的可伸缩性(在竞争激烈的情况下表现更好)。

主要特点

  1. 可重入性:同一个线程可以多次获取同一把锁,而不会产生死锁。
  2. 可中断:等待获取锁的线程可以被中断。
  3. 公平性选择:支持公平锁和非公平锁。公平锁保证等待时间最长的线程优先获取锁,但性能略低;非公平锁则提供更高的吞吐量。
  4. 超时获取:可以尝试在指定时间内获取锁,超时则放弃。
  5. 条件变量(Condition):一个锁可以关联多个条件变量,提供更灵活的线程等待/唤醒机制。

2. ReentrantLock基本使用

java
// 基本使用模式
Lock lock = new ReentrantLock();
lock.lock(); // 加锁
try {
    // 临界区代码
} finally {
    lock.unlock(); // 释放锁
}

3. 源码分析

ReentrantLock的核心是基于AbstractQueuedSynchronizer(AQS)实现的。在ReentrantLock中有三个内部类:Sync、NonfairSync和FairSync,其中Sync继承自AQS,NonfairSync和FairSync分别继承Sync,分别实现非公平锁和公平锁的获取逻辑。

3.1 Sync类分析

首先,我们来看Sync类。由于ReentrantLock是可重入锁,所以它使用一个state变量(继承自AQS)来表示锁的持有计数。当state为0时表示锁未被任何线程持有,大于0时表示被某个线程持有,并且值表示重入次数。

Sync类是一个抽象类,主要提供非公平尝试获取和释放锁的机制,但是FairSync会覆盖部分方法以实现公平获取。

3.2 NonfairSync(非公平锁)

非公平锁在获取锁时,不管等待队列中是否有其他线程在等待,都会直接尝试获取锁,如果获取失败,则加入队列等待。

java
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    // 非公平锁获取的入口方法
    final void lock() {
        // 非公平策略核心:直接进行一次CAS尝试获取锁
        if (compareAndSetState(0, 1)) { // 成功则设置当前线程为独占线程
            setExclusiveOwnerThread(Thread.currentThread());
        } else { // 失败后进入AQS标准的获取流程
            acquire(1); // 调用AQS.acquire(),最终会调用tryAcquire()
        }
    }

    // 重写AQS的tryAcquire方法
    protected final boolean tryAcquire(int acquires) {
        // 调用Sync类中定义的nonfairTryAcquire方法
        return nonfairTryAcquire(acquires);
    }
}

nonfairTryAcquire方法中(定义在Sync中):

java
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    
    // 当前锁未被任何线程持有,尝试获取锁
    if (c == 0) {
        // 非公平策略体现:直接进行CAS抢占
        // 如果成功,则设置当前线程为独占线程
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 可重入逻辑:如果当前线程已经是锁的持有者
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        
        // 重入次数溢出检测(MAX_COUNT = 65535)
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
            
        // 更新state值实现重入计数增加
        setState(nextc);
        return true;
    }
    
    // 返回false表示获取锁失败,主要有两种情况:
    // 1. 锁当前被其他线程持有
    // 2. CAS操作失败(存在并发竞争)
    return false;
}

3.3 FairSync(公平锁)

公平锁的获取方式:

java
/**
 * 公平锁实现,继承自Sync类
 * 公平锁严格按照FIFO顺序获取锁,避免线程饥饿
 */
static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    /**
     * 获取锁的入口方法
     * 直接调用AQS的acquire方法,公平锁不进行直接CAS尝试
     */
    final void lock() {
        acquire(1);  // 参数1表示请求获取锁
    }

    /**
     * 尝试获取锁的核心方法
     * @param acquires 请求获取锁的数量,通常为1
     * @return true表示获取成功,false表示失败
     */
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();  // 获取当前锁状态
        
        // 情况1:锁未被任何线程持有
        if (c == 0) {
            // 公平锁核心逻辑:必须先检查队列中是否有更早的等待线程
            if (!hasQueuedPredecessors() &&  // 队列中没有更早的线程
                compareAndSetState(0, acquires)) {  // CAS设置状态
                setExclusiveOwnerThread(current);  // 设置当前线程为锁持有者
                return true;
            }
        }
        // 情况2:锁已被当前线程持有(可重入)
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;  // 增加重入次数
            // 重入次数溢出检查(达到int最大值)
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);  // 更新状态
            return true;
        }
        
        // 获取锁失败的情况:
        // 1. 锁被其他线程持有
        // 2. 队列中有更早的等待线程
        // 3. CAS竞争失败
        return false;
    }
}

3.4 AQS同步队列管理

ReentrantLock 的等待队列管理由 AQS (AbstractQueuedSynchronizer) 实现,采用 CLH (Craig, Landin, and Hagersten) 队列的变体。队列通过两个关键指针和状态变量进行管理:

java
// 队列头节点,通常表示当前持有锁的节点
// 注意:头节点是延迟初始化的,只有第一次发生争用时才会创建
private transient volatile Node head;

// 队列尾节点,新节点会添加到尾部
private transient volatile Node tail;

// 锁状态:
// 0 - 表示锁未被任何线程持有
// >0 - 表示锁被某个线程持有,数值表示重入次数
private volatile int state;

Node 节点是 AQS 队列的基本单元,包含以下关键字段:

java
static final class Node {
    // 等待状态值:
    static final int CANCELLED =  1;  // 节点因超时或中断被取消
    static final int SIGNAL    = -1;  // 后继节点需要被唤醒
    static final int CONDITION = -2;  // 节点在条件队列中等待
    static final int PROPAGATE = -3;  // 共享模式下需要传播唤醒
    
    volatile int waitStatus;  // 当前节点的等待状态
    
    volatile Node prev;       // 前驱节点
    volatile Node next;       // 后继节点
    volatile Thread thread;   // 关联的线程
    
    Node nextWaiter;          // 用于区分共享/独占模式,或条件队列链接
    
    // 判断是否为共享模式节点
    final boolean isShared() {
        return nextWaiter == SHARED;
    }
    
    // 构造方法
    Node() {}                 // 用于建立初始头节点或共享标记
    Node(Thread thread, Node mode) {  // 用于addWaiter
        this.nextWaiter = mode;
        this.thread = thread;
    }
    Node(Thread thread, int waitStatus) { // 用于条件队列
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}

3.5 条件变量实现 (ConditionObject)

ReentrantLock 的条件变量是通过内部类 ConditionObject 实现的,它是 AQS 的内部类并实现了 Condition 接口。以下是其核心实现:

java
public class ConditionObject implements Condition {
    private transient Node firstWaiter;   // 条件队列头节点
    private transient Node lastWaiter;    // 条件队列尾节点
    
    /**
     * 使当前线程等待,直到被signal或中断
     * 1. 创建节点加入条件队列
     * 2. 完全释放锁
     * 3. 阻塞直到被转移到同步队列
     * 4. 重新获取锁
     */
    public final void await() throws InterruptedException {
        if (Thread.interrupted())         // 检查中断状态
            throw new InterruptedException();
            
        Node node = addConditionWaiter(); // 创建新节点加入条件队列尾部
        int savedState = fullyRelease(node); // 完全释放锁,返回释放前的state值
        int interruptMode = 0;
        
        // 循环检查是否被转移到同步队列
        while (!isOnSyncQueue(node)) {
            LockSupport.park(this);       // 阻塞当前线程
            // 检查中断状态,可能被signal或中断唤醒
            if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                break;
        }
        
        // 被唤醒后,尝试获取锁并处理中断
        if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
            interruptMode = REINTERRUPT;
            
        // 清理取消的等待节点    
        if (node.nextWaiter != null)
            unlinkCancelledWaiters();
            
        // 根据中断模式处理中断    
        if (interruptMode != 0)
            reportInterruptAfterWait(interruptMode);
    }

    /**
     * 唤醒条件队列中等待时间最长的线程
     * 1. 检查当前线程是否持有锁
     * 2. 将条件队列头节点转移到同步队列
     */
    public final void signal() {
        if (!isHeldExclusively())         // 检查是否独占模式
            throw new IllegalMonitorStateException();
            
        Node first = firstWaiter;
        if (first != null)
            doSignal(first);              // 从条件队列头开始唤醒
    }
}

4. 核心设计原理

4.1 可重入实现机制

ReentrantLock 通过维护两个关键状态实现可重入:

  • state:记录锁被获取的次数
  • exclusiveOwnerThread:记录当前持有锁的线程

当线程第一次获取锁时,state=1;同一线程再次获取锁时,state递增;释放锁时state递减,直到state=0时锁才完全释放。

4.2 公平性与非公平性差异

非公平锁

  • 新线程可以直接尝试获取锁,不必排队
  • 可能导致"饥饿"现象
  • 吞吐量通常更高

公平锁

  • 严格按照FIFO顺序获取锁
  • 避免饥饿问题
  • 吞吐量通常较低

4.3 性能优化点

  • CAS操作:使用compareAndSetState实现无锁化的状态变更
  • 自旋优化:在入队前短暂自旋尝试获取锁
  • 队列管理优化:通过CLH队列减少争用
  • 延迟初始化:head和tail节点按需创建

5. 使用示例与最佳实践

基本使用模式

java
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
    // 临界区代码
} finally {
    lock.unlock();
}

高级特性使用

尝试获取锁

java
if (lock.tryLock(1, TimeUnit.SECONDS)) {
    try {
        // 获取锁成功
    } finally {
        lock.unlock();
    }
} else {
    // 获取锁超时
}

可中断获取锁

java
try {
    lock.lockInterruptibly();
    // 临界区代码
} catch (InterruptedException e) {
    // 处理中断
} finally {
    if (lock.isHeldByCurrentThread()) {
        lock.unlock();
    }
}

条件变量使用

java
Condition condition = lock.newCondition();
lock.lock();
try {
    while (!conditionSatisfied) {
        condition.await();
    }
    // 处理条件满足的情况
} finally {
    lock.unlock();
}

6. 与synchronized的对比

特性ReentrantLocksynchronized
实现机制Java代码实现,基于AQSJVM内置实现
锁获取方式显式调用lock()/unlock()隐式获取和释放
可中断支持不支持
超时机制支持不支持
公平性可选择公平或非公平非公平
条件变量支持多个Condition单个等待队列
性能高竞争下表现更好低竞争下性能更好
代码复杂度需要手动释放锁自动释放

7. 典型应用场景

  • 需要可中断锁的场景:如取消长时间等待的任务
  • 需要尝试获取锁的场景:避免死锁或长时间等待
  • 需要公平性的场景:确保线程按顺序获取资源
  • 需要多个条件队列的场景:如生产者-消费者模型
  • 需要锁细粒度控制的场景:如锁分段技术

8. 常见问题与解决方案

忘记释放锁

  • 总是使用try-finally块确保锁释放
  • 使用静态代码分析工具检查

死锁问题

  • 使用tryLock设置超时
  • 保持一致的锁获取顺序

性能问题

  • 评估是否真的需要锁
  • 考虑减小临界区范围
  • 在低竞争场景考虑使用synchronized

条件变量误用

  • 总是使用while循环检查条件
  • 确保在调用await()前持有锁

9. 总结

ReentrantLock 是 Java 并发编程中的重要工具,它提供了比 synchronized 更丰富的功能特性。通过深入理解其实现原理,开发者可以:

  • 更合理地选择锁策略(公平/非公平)
  • 更高效地使用高级特性(可中断、超时等)
  • 更好地诊断和解决并发问题
  • 在适当场景下获得更好的性能表现

掌握 ReentrantLock 的源码实现,不仅有助于正确使用该工具,也是理解 Java 并发框架设计思想的重要途径。