源码解读 | Java中ReentrantLock的实现原理

发布于:2024-08-02 ⋅ 阅读:(66) ⋅ 点赞:(0)

本文将介绍Java中ReentrantLock的实现原理,从源码层面讲解公平锁和非公平锁的加锁、释放锁的流程,以及条件变量的实现。

ReentrantLock 依赖关系如下图所示:

在这里插入图片描述

非公平锁实现原理

ReentrantLock 默认采用非公平锁。

// ReentrantLock
public ReentrantLock() {
   
    sync = new NonfairSync();
}
加锁流程

ReentrantLock 的 lock 方法通过同步器的 lock 方法实现。

// ReentrantLock
public void lock() {
   
    sync.lock();
}

同步器的 lock 方法会先调用 initialTryLock() 方法,如果失败,则调用 acquire() 方法。

// Sync extends AbstractQueuedSynchronizer
final void lock() {
   
    if (!initialTryLock())
        acquire(1);
}
// AbstractQueuedSynchronizer
public final void acquire(int arg) {
   
    // 尝试获取锁
    if (!tryAcquire(arg))
        // 获取锁失败则加入的等待队列
        // null, 自定义参数, 非共享锁, 不可打断, 无时限, 时限 
        acquire(null, arg, false, false, false, 0L);
}

NonfairSync 的 initialTryLock 分两步:尝试获取锁、尝试重入。

NonfairSync 的 tryAcquire 方法被调用前,必定会调用 initialTryLock() 方法检查锁是否被当前线程持有,也即,调用 tryAcquire 方法时,锁必定未被当前线程持有。因此,当未有线程持有锁时,tryAcquire 才能尝试获取锁。

// NonfairSync extends Sync
static final class NonfairSync extends Sync {
   
    
    // 初次尝试获取锁
    final boolean initialTryLock() {
   
        Thread current = Thread.currentThread();
        // 通过CAS尝试获取锁
        if (compareAndSetState(0, 1)) {
   
            // 将锁的持有者设为当前线程
            setExclusiveOwnerThread(current);
            return true;
        } 
        // 尝试获取锁失败,判断锁的持有者是否为当前线程
        else if (getExclusiveOwnerThread() == current) {
   
            // 锁重入
            int c = getState() + 1;
            // 整数溢出
            if (c < 0) 
                throw new Error("Maximum lock count exceeded");
            // 设置锁的状态:state 表示重入次数
            setState(c);
            return true;
        } else
            return false;
    }

    // 非初次尝试获取锁
    protected final boolean tryAcquire(int acquires) {
   
        // 未有线程持有锁时,当前线程尝试获取锁
        if (getState() == 0 && compareAndSetState(0, acquires)) {
   
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }
}

AbstractQueuedSynchronizer 的 acquire 方法负责将线程加入等待队列,acquire 方法的主要执行步骤如下:

  1. 若非第一个等待线程且前驱存在,则检查当前节点前驱是否已被取消(前驱线程被取消)
    • 若已被取消,则需要从队尾开始,往前清理已取消的节点,进入下一轮循环。
    • 若未被取消,检查当前线程是否已成为第一个线程,若是,则自旋等待,进入下一轮循环
  2. 若是第一个等待线程或者前驱不存在,则尝试获取锁,获取锁成功则直接返回 1 表示获取锁成功。
  3. 准备将当前线程加入等待队列
    • 3.1 若等待队列未创建,则创建等待队列,进入下一轮循环
    • 3.2 否则,若节点未创建,则创建节点,进入下一轮循环
    • 3.3 否则,若节点信息未设置,则设置节点信息(包括将节点加入队尾),进入下一轮循环
    • 3.4 否则,若为第一个等待线程且自旋次数不为 0,提示 JVM 当前线程正在忙等,进入下一轮循环
    • 3.5 否则,若节点状态为 0(默认),则将节点状态设为 1(WAITING)
    • 3.6 否则,重置自旋自旋次数,当前线程陷入等待,被唤醒后将等待状态置为 0。
// AbstractQueuedSynchronizer
final int acquire(
    // 尝试获取锁的节点
    Node node, 
    // 自定义参数
    int arg, 
    // 是否为共享锁
    boolean shared,
    // 是否可打断
    boolean interruptible, 
    // 是否带时限
    boolean timed,
    // 时限
    long time
) {
   
    Thread current = Thread.currentThread();
    byte spins = 0, postSpins = 0;  
    boolean interrupted = false, first = false;
    Node pred = null;               
    
    for (;;) {
   
        // 检查前驱是否被取消
        if (
            // 非第一个等待线程
            !first && 
            // 前驱存在
            // 除非本方法被ConditionNode.await()方法调用,否则node初始必为null
            // 也即,此条件在第一轮循环必为false
            (pred = (node == null) ? null : node.prev) != null &&
            // 非第一个等待线程
            !(first = (head == pred))
        ) {
   
            if (pred.status < 0) {
   
                // 若前驱被取消,则需要清理队列中取消的线程
                // 此举是为确保给当前节点的前驱能唤醒当前节点
                cleanQueue(); 
                continue;
            } else if (pred.prev == null) {
   
                // 若前驱的前驱为null,说明当前线程是第一个线程
                // !first 和 pred.prev == null 之间,head的值被修改,导致当前线程成为第一个线程
                // 第一个线程自旋等待,以减少线程切换的开销
                Thread.onSpinWait();
                continue;
            }
        }
        // 尝试获取锁
        if (
            // 第一个线程 或者 前驱不存在
            first || pred == null
        ) {
   
            boolean acquired;
            // 尝试获取锁
            try {
   
                if (shared)
                    acquired = (tryAcquireShared(arg) >= 0);
                else
                    acquired = tryAcquire(arg);
            } catch (Throwable ex) {
   
                cancelAcquire

网站公告

今日签到

点亮在社区的每一天
去签到