ReentrantLock 源码解析:AQS 核心原理

一、前言

在上一篇文章中,我们了解了 ReentrantLock 的核心特性与基本使用,感受到了它作为显式锁的灵活与强大。但很多开发者可能会有疑问:ReentrantLock 的可重入性、公平 / 非公平锁策略、线程排队机制,到底是在底层如何实现的?

答案其实很简单:ReentrantLock 的所有核心逻辑,都依赖于 Java 并发包的 "基石"------AbstractQueuedSynchronizer(简称 AQS)。AQS 是 JUC 包中大多数同步工具(如 ReentrantLock、CountDownLatch、Semaphore)的底层实现框架,它定义了一套通用的同步状态管理、线程排队、锁获取与释放的模板逻辑,子类只需重写少量核心方法,就能快速实现一款自定义同步工具。

本文作为 ReentrantLock 源码解析的第一篇铺垫文章,将重点拆解 AQS 的核心设计思想、数据结构与核心方法,帮大家打通 "ReentrantLock 用法" 到 "底层原理" 的认知壁垒,为后续解读公平锁、非公平锁的具体实现打下坚实基础。

二、AQS 核心设计思想

AQS 的核心设计可以概括为" 一个状态变量 + 一个双端队列 + 模板方法模式 ",三者协同工作,实现线程的安全同步与排队管理,我们逐一拆解:

1. 核心目标:解决 "线程竞争同步状态" 的问题

AQS 的本质是通过管理一个 "同步状态(state)",来控制多个线程对共享资源的访问权限。这个同步状态是 AQS 的核心,不同的同步工具对 state 的含义定义不同:

  • **对于 ReentrantLock:**state 表示 "锁的持有状态",0 表示锁未被持有,大于 0 表示锁被持有(数值等于线程重入的次数);

  • **对于 Semaphore:**state 表示 "可用的许可数量";

  • **对于 CountDownLatch:**state 表示 "倒计时计数器的值"。

简单来说,所有线程的竞争,本质上都是在竞争这个 state 的修改权 ------ 谁能成功修改 state,谁就能获得对应的同步权限(比如获取锁、获取许可)。

2. 核心设计:模板方法模式(骨架 + 子类实现)

AQS 采用 "模板方法模式" 设计,这是它能成为 "同步工具框架" 的关键原因,具体分为两层:

  • **上层(AQS 本身):**定义了一套完整的 "锁获取、锁释放、线程排队、线程唤醒" 的骨架流程(模板方法),这些方法是线程安全的,且逻辑通用;

  • **下层(子类,如 ReentrantLock 的 Sync 类):**只需重写少量核心抽象方法(如 tryAcquire、tryRelease),实现对 state 的具体修改逻辑,无需关心线程排队、唤醒的细节。

这种设计的优势的是 "复用代码"------JUC 包中所有同步工具,都无需重复实现线程排队、唤醒逻辑,只需聚焦于自身的同步状态管理,极大简化了自定义同步工具的开发。

3. 核心保障:volatile + CAS + LockSupport

AQS 能保证线程安全,依赖于三大核心技术,分别解决不同问题:

  • **volatile:**修饰同步状态 state,保证 state 的可见性(一个线程修改 state 后,其他线程能立即看到最新值),避免线程因 "内存可见性问题" 导致的同步错误;

  • **CAS(CompareAndSwap):**用于原子性修改 state 和队列节点,避免多线程并发修改时出现数据错乱(比如多个线程同时尝试抢占锁,通过 CAS 保证只有一个线程能成功修改 state);

  • **LockSupport:**用于实现线程的阻塞(park ())和唤醒(unpark ()),是 AQS 线程排队机制的底层工具(替代了 Object 的 wait ()/notify (),更灵活)。

三、AQS 核心数据结构

AQS 的核心数据结构有两个: 同步状态 state 和 双端队列(CLH 队列) ,二者共同支撑起线程的竞争与排队逻辑。

1. 同步状态:volatile int state

java 复制代码
// AQS 核心状态变量,volatile 保证可见性
private volatile int state;

**访问方式:**AQS 提供了三个核心方法来操作 state,均为线程安全:

  1. **getState ():**获取当前 state 的值(只读,volatile 保证可见性);

  2. **setState (int newState):**设置 state 的值(仅在当前线程已获取同步权限时使用);

  3. **compareAndSetState (int expect, int update):**CAS 原子修改 state(核心方法),只有当 state 当前值等于 expect 时,才会更新为 update,返回 true,否则返回 false(线程竞争的核心逻辑)。

2. 双端队列:CLH 队列(线程排队的 "等候区")

当多个线程竞争同步状态失败时,不会立即退出,而是会被封装成一个 "节点(Node)",加入到 AQS 的双端队列中排队等待 ------ 这个队列就是 CLH 队列(基于链表实现的双端队列,FIFO 顺序,即 "先来后到")。

(1)Node 节点的核心结构(简化版)

每个线程对应一个 Node 节点,Node 是 AQS 的内部类,核心属性如下(聚焦与 ReentrantLock 相关的属性):

java 复制代码
static final class Node {
    // 线程状态:CANCELLED(已取消)、SIGNAL(等待被唤醒)、WAITING(等待中)等
    volatile int waitStatus;
    // 前驱节点(前一个排队的线程)
    volatile Node prev;
    // 后继节点(后一个排队的线程)
    volatile Node next;
    // 当前节点对应的线程
    volatile Thread thread;
    // 关联的 Condition 对象(后续讲解 Condition 时用到)
    Node nextWaiter;
}

**核心作用:**通过 prev 和 next 维护链表结构,实现线程排队;通过 waitStatus 标记线程的状态,避免唤醒无效线程(比如已取消的线程)。

(2)CLH 队列的核心特性

  1. **双端队列:**支持从队首(head)获取节点、从队尾(tail)添加节点,操作高效;

  2. **虚拟头节点:**队列初始化时,会创建一个 "虚拟头节点"(不对应任何线程),目的是简化队列的添加、删除逻辑(避免处理空队列的边界问题);

  3. **FIFO 顺序:**线程排队遵循 "先来后到",队首节点是最先等待的线程,优先获得被唤醒的机会(公平锁的核心依据);

  4. **线程阻塞:**队列中的线程并非一直自旋等待,而是会通过 LockSupport.park () 阻塞,避免 CPU 空转,提升性能。

(3)CLH 队列的简单示意图(结合 ReentrantLock 场景)

  1. 当线程 1 获取锁成功时,会从队列中移除,head 指向 Node2;

  2. 当线程 1 释放锁时,会唤醒 Node2 对应的线程,让其尝试获取锁。

3. 两种同步模式(AQS 的核心分类)

AQS 支持两种同步模式,不同模式对应不同的同步场景,ReentrantLock 属于 "独占模式":

  • **独占模式(Exclusive):**同一时刻,只有一个线程能获取同步状态(即独占资源),ReentrantLock、Synchronized 均属于这种模式;

  • **共享模式(Shared):**同一时刻,多个线程能同时获取同步状态(即共享资源),CountDownLatch、Semaphore 均属于这种模式。

本文重点聚焦 "独占模式",因为这是 ReentrantLock 的核心依赖。

四、AQS 核心方法解析(独占模式)

AQS 的核心方法分为 "模板方法"(AQS 定义,子类无需重写)和 "抽象方法"(子类必须重写,ReentrantLock 的核心逻辑所在),我们重点解析与 ReentrantLock 相关的 "独占模式" 方法。

1. 模板方法:acquire (int arg)(独占模式获取同步状态)

acquire(int arg) 是 AQS 提供的 "独占模式获取同步状态" 的核心模板方法,ReentrantLock 调用 lock() 方法时,最终会调用该方法。其核心流程是 " 尝试获取锁 → 获取失败则排队 → 排队后等待被唤醒 ",完整流程如下(结合源码逻辑简化,便于理解):

java 复制代码
// AQS 独占模式获取同步状态的模板方法,arg 是获取状态的参数(ReentrantLock 中 arg=1)
public final void acquire(int arg) {
    // 1. 尝试获取同步状态(调用子类重写的 tryAcquire 方法)
    if (!tryAcquire(arg) &&
        // 2. 尝试失败 → 将当前线程封装成 Node,加入 CLH 队列尾部
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
        // 3. 如果线程在排队中被中断,标记当前线程为中断状态
        selfInterrupt();
    }
}

我们拆解每一步的核心逻辑(无需纠结源码细节,聚焦流程):

  1. **tryAcquire(arg):**核心抽象方法 ,由子类(ReentrantLock 的 Sync 类)重写,逻辑是 "尝试原子性修改 state,获取同步状态"(比如 ReentrantLock 中,就是尝试将 state 从 0 改为 1,抢占锁);如果返回 true,说明获取成功,线程直接执行后续逻辑;如果返回 false,说明获取失败,进入下一步;

  2. **addWaiter (Node.EXCLUSIVE):**将当前线程封装成 "独占模式" 的 Node 节点,加入 CLH 队列的尾部(通过 CAS 保证队列操作的线程安全);

  3. **acquireQueued (Node node, arg):**让当前节点在队列中排队,同时阻塞线程(通过 LockSupport.park ()),直到被前驱节点唤醒后,再次尝试获取同步状态;

  4. **selfInterrupt ():**如果线程在阻塞过程中被中断,唤醒后标记线程的中断状态(因为 AQS 会忽略线程中断,需要手动标记,供后续业务处理)。

**核心总结:**acquire(arg) 定义了 "获取锁 → 排队 → 阻塞" 的完整骨架,子类只需实现 tryAcquire(arg) ,就能实现自己的 "锁抢占逻辑"。

2. 模板方法:release (int arg)(独占模式释放同步状态)

release(int arg) 是 AQS 提供的 "独占模式释放同步状态" 的核心模板方法,ReentrantLock 调用 unlock() 方法时,最终会调用该方法。其核心流程是 " 尝试释放同步状态 → 释放成功则唤醒队列首节点的线程",完整流程如下:

java 复制代码
// AQS 独占模式释放同步状态的模板方法,arg 是释放状态的参数(ReentrantLock 中 arg=1)
public final boolean release(int arg) {
    // 1. 尝试释放同步状态(调用子类重写的 tryRelease 方法)
    if (tryRelease(arg)) {
        // 2. 释放成功 → 获取队列头节点
        Node h = head;
        // 3. 如果头节点存在,且状态不是 0,唤醒头节点的后继节点(下一个排队的线程)
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

拆解核心逻辑:

  1. tryRelease(arg): 核心抽象方法 ,由子类(ReentrantLock 的 Sync 类)重写,逻辑是 "尝试修改 state,释放同步状态"(比如 ReentrantLock 中,就是将 state 减 1,直到 state 为 0 时,真正释放锁);如果返回 true,说明释放成功,进入下一步;如果返回 false,说明释放失败(比如线程未持有锁,却尝试释放);

  2. **unparkSuccessor (h):**唤醒头节点的后继节点(下一个排队的线程),通过 LockSupport.unpark () 实现 ------ 被唤醒的线程会从 acquireQueued() 方法的阻塞中恢复,再次尝试获取同步状态(抢占锁)。

**核心总结:**release(arg) 定义了 "释放锁 → 唤醒线程" 的完整骨架,子类只需实现 tryRelease(arg) ,就能实现自己的 "锁释放逻辑"。

3. 抽象方法:tryAcquire (int arg) + tryRelease (int arg)(子类核心实现)

这两个方法是 AQS 的抽象方法,也是 ReentrantLock 与 AQS 对接的核心 ------ReentrantLock 的内部类 Sync (继承 AQS),正是通过重写这两个方法,实现了锁的获取与释放逻辑。

(1)tryAcquire(int arg)

  • **核心作用:**尝试独占式获取同步状态(抢占锁),由子类重写;

  • ReentrantLock 中的逻辑(提前铺垫):

**1.公平锁:**先判断队列中是否有等待线程,若有则排队,若无则通过 CAS 修改 state;

**2.非公平锁:**直接通过 CAS 修改 state,抢占失败再判断是否重入,最后排队;

  • **核心要求:**必须保证线程安全(通过 CAS 操作 state)。

(2)tryRelease(int arg)

  • **核心作用:**尝试独占式释放同步状态(释放锁),由子类重写;

  • ReentrantLock 中的逻辑(提前铺垫):

1.线程持有锁时,将 state 减 1(因为支持重入,state 是重入次数);

2.当 state 减到 0 时,清空 "当前持有锁的线程",返回 true(释放成功);

3.若 state 未减到 0,说明线程仍持有锁(重入状态),返回 false;

  • **核心要求:**必须保证只有 "持有锁的线程" 才能释放锁,避免非法释放。

4. 辅助方法:LockSupport.park () /unpark (Thread t)

LockSupport 是 AQS 实现线程阻塞与唤醒的底层工具,比 Object 的 wait ()/notify () 更灵活,核心特性如下:

  • **park ():**阻塞当前线程,直到其他线程调用 unpark (当前线程) 或当前线程被中断;

  • **unpark (Thread t):**唤醒指定的线程 t,让其从 park () 阻塞中恢复;

  • **核心优势:**无需像 wait ()/notify () 那样依赖 synchronized 锁,且能精准唤醒指定线程(AQS 队列中,就是通过 unparkSuccessor () 调用 unpark (),唤醒下一个排队的线程)。

补充:AQS 队列中的线程,并非一直自旋等待,而是通过 LockSupport.park() 阻塞,这样能避免 CPU 空转 ------ 这是 AQS 性能优化的关键一点。

五、AQS 与 ReentrantLock 的关系

理解了 AQS 的核心原理后,我们再回到 ReentrantLock,搞清楚二者的 "联动关系"------ReentrantLock 本质上是 AQS 的 "子类实现",通过内部类 Sync 继承 AQS,重写核心方法,实现锁的逻辑。

1. ReentrantLock 的内部类结构(简化版)

ReentrantLock 内部有三个核心内部类,均与 AQS 相关:

java 复制代码
public class ReentrantLock implements Lock {
    // 核心内部类,继承 AQS,重写 tryAcquire、tryRelease 等方法
    private final Sync sync;
    // 非公平锁的实现类,继承 Sync
    static final class NonfairSync extends Sync { ... }
    // 公平锁的实现类,继承 Sync
    static final class FairSync extends Sync { ... }
    // 无参构造函数:默认创建非公平锁
    public ReentrantLock() {
        sync = new NonfairSync();
    }
    // 有参构造函数:true=公平锁,false=非公平锁
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    // ReentrantLock 的 lock() 方法,本质是调用 sync 的 lock() 方法
    public void lock() {
        sync.lock();
    }
    // ReentrantLock 的 unlock() 方法,本质是调用 sync 的 release(1) 方法
    public void unlock() {
        sync.release(1);
    }
}

2. 二者联动的核心流程(以 ReentrantLock 非公平锁为例)

  1. 线程调用 ReentrantLock.lock() :本质是调用 NonfairSync.lock() ;

  2. NonfairSync.lock()中,会先尝试通过 CAS 修改 AQS 的 state(0→1),抢占锁;

  3. 抢占成功:设置当前线程为 "独占线程",锁获取成功;

  4. 抢占失败:调用 AQS 的 acquire(1) 方法,进入线程排队流程(封装 Node、加入 CLH 队列、阻塞线程);

  5. 线程调用 ReentrantLock.unlock() :本质是调用 AQS 的 release(1) 方法;

  6. release(1)中,调用 NonfairSync.tryRelease(1) ,将 state 减 1;

  7. 若 state 减到 0,释放锁成功,唤醒 CLH 队列中首节点的后继线程,让其尝试抢占锁。

核心总结: ReentrantLock 负责对外提供锁的使用接口(lock ()、unlock ()),AQS 负责底层的状态管理、线程排队、唤醒逻辑,二者通过 Sync 类联动------ReentrantLock 是 "上层封装",AQS 是 "底层支撑"。

六、总结

本文详细拆解了 AQS 的核心原理,包括核心设计思想(模板方法模式)、核心数据结构(state 状态变量 + CLH 双端队列)、核心方法(acquire、release、tryAcquire、tryRelease),以及 AQS 与 ReentrantLock 的联动关系。

核心要点总结:

  1. AQS 是 JUC 同步工具的底层框架,核心是 "状态管理 + 线程排队";

  2. ReentrantLock 是 AQS 的子类实现,通过 Sync 类重写 tryAcquire、tryRelease,实现锁的逻辑;

  3. 线程竞争锁的本质,是竞争 AQS 中 state 的修改权,竞争失败则进入 CLH 队列阻塞,等待被唤醒;

  4. 模板方法模式是 AQS 的核心设计,让子类无需关心线程排队、唤醒细节,只需聚焦 state 的操作。

相关推荐
悟能不能悟14 小时前
openfeign 返回void和ResponseEntity的区别
java
董世昌4114 小时前
如何声明一个类?类如何继承?
java·开发语言·前端
企微自动化14 小时前
企业微信 API 开发:如何实现外部群消息主动推送
java·开发语言·spring
艾莉丝努力练剑14 小时前
【QT】初识QT:背景介绍
java·运维·数据库·人工智能·qt·安全·gui
糯诺诺米团14 小时前
C++多线程打包成so给JAVA后端(Ubuntu)<2>
java·开发语言·c++
一线大码14 小时前
后端分层架构规范和标准包结构
java·后端
南屿欣风14 小时前
Maven 聚合工程打包报错:Unable to find main class 快速解决
java·maven
洛_尘14 小时前
Java EE进阶7:Spring Boot 日志
java·spring boot·java-ee
耘田14 小时前
 macOS Launch Agent 定时任务实践指南
java·开发语言·macos