乐于分享
好东西不私藏

AQS原理+ReentrantLock源码+与synchronized深度对比

AQS原理+ReentrantLock源码+与synchronized深度对比

并发编程是Java高级开发的核心门槛,而AQS、ReentrantLock、synchronized则是并发领域的“铁三角”。很多开发者只会用ReentrantLock和synchronized做同步,却不懂其底层依赖的AQS框架;面试时被问“ReentrantLock和synchronized的区别”“AQS原理是什么”,往往只能答表面,无法触及核心。

一、前置认知:为什么要懂AQS?

AQS(AbstractQueuedSynchronizer),即抽象队列同步器,是Java并发包的核心基石。ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch等并发工具,底层都依赖AQS实现

简单来说,AQS就是一个“模板框架”,它定义了一套并发同步的规范,封装了“线程排队、锁竞争、唤醒机制”等通用逻辑,开发者只需重写少量方法,就能快速实现自定义的同步锁或同步器,无需重复编写复杂的排队和唤醒逻辑。

核心价值:解耦“通用并发逻辑”与“具体锁实现”,让并发工具的开发更高效、更规范。

请在微信客户端打开

二、AQS核心原理

AQS的核心设计可以概括为:一个状态变量 + 一个双向阻塞队列 + 一套模板方法,三者协同实现线程的同步与竞争。

1. 核心组件1:状态变量(state)

AQS用一个volatile修饰的int变量state,来表示“同步状态”,其含义由具体的实现类(如ReentrantLock)定义,核心作用是标记锁的占用情况和重入次数:

  • state = 0:表示锁处于空闲状态,无线程占用;
  • state > 0:表示锁已被占用,state的值代表“重入次数”(如state=2,说明当前线程重入了2次);
  • state < 0:无效状态,通常不会出现。

AQS提供了3个核心方法,用于操作state(保证原子性和可见性):

// 获取当前同步状态(volatile保证可见性)protected final int getState() {    return state;}// 设置同步状态(仅在当前线程持有锁时使用,无并发竞争)protected final void setState(int newState) {    state = newState;}// CAS原子更新同步状态(核心,用于锁竞争时的状态修改)protected final boolean compareAndSetState(int expect, int update) {    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);}

unsafe是Java底层的Unsafe类,提供了CAS原子操作,保证state的修改是线程安全的;stateOffset是state变量在AQS类中的内存偏移量,用于Unsafe直接操作内存。

2. 核心组件2:双向阻塞队列(CLH队列)

当多个线程竞争锁失败时,AQS会将这些线程封装成“节点(Node)”,加入到一个双向虚拟队列中(CLH队列变体),让线程阻塞等待;当锁释放时,再从队列中唤醒一个线程竞争锁。

CLH队列的核心特点:

  • 虚拟队列:队列本身没有实际的容器,而是通过节点的prev(前驱)和next(后继)指针,形成双向链表结构;
  • 节点状态:每个Node都有一个waitStatus变量,标记节点的状态(如等待中、已取消、已唤醒),用于控制线程的阻塞和唤醒;
  • FIFO原则:队列遵循“先进先出”,保证线程竞争锁的顺序(公平锁依赖此特性,非公平锁可打破此顺序)。

Node节点核心结构:

static final class Node {    // 标记节点类型:独占锁(ReentrantLock用此类型)、共享锁(Semaphore用此类型)    static final Node EXCLUSIVE = null;    static final Node SHARED = new Node();    // 节点状态:CANCELLED(1,已取消)、SIGNAL(-1,等待唤醒)、CONDITION(-2,条件等待)等    volatile int waitStatus;    // 前驱节点    volatile Node prev;    // 后继节点    volatile Node next;    // 当前节点对应的线程    volatile Thread thread;    // 条件队列相关(ReentrantLock的Condition依赖)    Node nextWaiter;}

队列工作流程:

  1. 线程竞争锁失败 → 封装成Node节点;
  2. 通过CAS操作,将节点加入队列尾部;
  3. 当前线程调用LockSupport.park()方法,进入阻塞状态;
  4. 持有锁的线程释放锁时,唤醒队列头部的节点,该节点对应的线程重新竞争锁。

3. 核心组件3:模板方法模式

AQS的核心设计模式是模板方法模式:它定义了一套“锁获取、锁释放”的通用流程(模板方法),将具体的“锁竞争、锁释放”逻辑,交给子类(如ReentrantLock的Sync内部类)重写。

AQS的核心模板方法(无需重写,直接调用):

  • acquire(int arg):独占式获取锁(ReentrantLock的lock()方法底层调用此方法);
  • release(int arg):独占式释放锁(ReentrantLock的unlock()方法底层调用此方法);
  • acquireShared(int arg):共享式获取锁(Semaphore的acquire()方法底层调用);
  • releaseShared(int arg):共享式释放锁(Semaphore的release()方法底层调用)。

AQS要求子类重写的核心方法(仅需重写这几个,其余逻辑由AQS封装):

  • tryAcquire(int arg):独占式尝试获取锁(ReentrantLock的公平/非公平锁核心实现);
  • tryRelease(int arg):独占式尝试释放锁;
  • tryAcquireShared(int arg):共享式尝试获取锁;
  • tryReleaseShared(int arg):共享式尝试释放锁;
  • isHeldExclusively():判断当前线程是否独占持有锁(用于可重入性判断)。

模板方法流程(以acquire()为例):

// AQS的acquire方法(模板方法,定义了独占式获取锁的完整流程)publicfinalvoidacquire(int arg){    // 1. 尝试获取锁(子类重写tryAcquire),获取成功则直接返回    if (!tryAcquire(arg) &&        // 2. 尝试获取锁失败,将当前线程封装成Node,加入队列        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {        // 3. 如果线程在队列中被中断,标记当前线程为中断状态        selfInterrupt();    }}

总结AQS核心:用state控制锁的状态,用CLH队列管理等待线程,用模板方法封装通用流程,子类重写具体的锁逻辑——这就是ReentrantLock能够实现公平锁、非公平锁、可重入性的底层基础。

三、ReentrantLock源码解析(基于AQS的实现)

ReentrantLock(可重入锁)是AQS最经典的实现类,核心特性:可重入、支持公平锁和非公平锁(默认非公平锁)、支持中断、支持条件等待(Condition)。

ReentrantLock的核心结构:内部维护了一个Sync抽象内部类(继承AQS),Sync有两个子类——NonfairSync(非公平锁)和FairSync(公平锁),ReentrantLock的所有锁操作,最终都委托给Sync子类实现。

1. ReentrantLock构造方法(公平/非公平锁选择)

// 无参构造:默认非公平锁(性能更优,大多数场景首选)publicReentrantLock() {    sync = new NonfairSync();}// 有参构造:true=公平锁,false=非公平锁publicReentrantLock(boolean fair) {    sync = fair ? new FairSync() : new NonfairSync();}

公平锁与非公平锁的核心区别:公平锁严格遵循“先到先得”(CLH队列顺序),非公平锁允许“插队”(新线程可直接竞争锁,无需排队)。非公平锁性能更优,因为减少了线程排队、唤醒的开销;公平锁则保证了线程竞争的公平性,避免线程饥饿。

2. 核心方法:lock()(获取锁)

ReentrantLock的lock()方法,本质是调用Sync子类的lock()方法,再间接调用AQS的acquire(1)方法(arg=1表示每次获取锁,state+1)。

(1)非公平锁(NonfairSync)lock()

static final class NonfairSync extends Sync {    private static final long serialVersionUID = 7316153563782823691L;    // 非公平锁获取锁的核心方法    final void lock() {        // 1. 尝试CAS抢锁:state从0→1,如果成功,标记当前线程为锁持有者        if (compareAndSetState(01))            setExclusiveOwnerThread(Thread.currentThread());        else            // 2. 抢锁失败,调用AQS的acquire(1),进入队列等待            acquire(1);    }    // 重写AQS的tryAcquire方法(非公平锁的锁竞争逻辑)    protected final boolean tryAcquire(int acquires) {        return nonfairTryAcquire(acquires);    }}

核心逻辑:非公平锁的“插队”特性体现在第一步——即使队列中有等待的线程,新线程也会先尝试CAS抢锁,抢锁成功则直接持有锁,无需排队;只有抢锁失败,才会加入队列。

(2)nonfairTryAcquire()(非公平锁尝试获取锁)

// 非公平锁尝试获取锁的核心逻辑(可重入性实现)final boolean nonfairTryAcquire(int acquires) {    final Thread current = Thread.currentThread();    int c = getState();    // 1. 如果锁空闲(state=0),直接CAS抢锁    if (c == 0) {        if (compareAndSetState(0, acquires)) {            setExclusiveOwnerThread(current);            return true;        }    }    // 2. 如果当前线程就是锁持有者(可重入性),state+1    else if (current == getExclusiveOwnerThread()) {        int nextc = c + acquires;        if (nextc < 0// 防止重入次数溢出            throw new Error("Maximum lock count exceeded");        setState(nextc); // 无需CAS,因为当前线程已持有锁,无并发竞争        return true;    }    // 3. 锁被其他线程持有,抢锁失败    return false;}

(3)公平锁(FairSync)lock()

static final class FairSync extends Sync {    private static final long serialVersionUID = -3000897897090466540L;    final void lock() {        // 公平锁不“插队”,直接调用AQS的acquire(1),进入队列排队        acquire(1);    }    // 重写AQS的tryAcquire方法(公平锁的锁竞争逻辑)    protected final boolean tryAcquire(int acquires) {        final Thread current = Thread.currentThread();        int c = getState();        if (c == 0) {            // 关键区别:公平锁抢锁前,先判断队列中是否有等待的线程            // hasQueuedPredecessors():判断当前线程是否是队列的第一个节点            if (!hasQueuedPredecessors() &&                compareAndSetState(0, acquires)) {                setExclusiveOwnerThread(current);                return true;            }        }        // 可重入性逻辑(与非公平锁一致)        else if (current == getExclusiveOwnerThread()) {            int nextc = c + acquires;            if (nextc < 0)                throw new Error("Maximum lock count exceeded");            setState(nextc);            return true;        }        return false;    }}

核心区别:公平锁在抢锁前,会通过hasQueuedPredecessors()方法判断“队列中是否有比当前线程更早等待的线程”,如果有,当前线程不会抢锁,直接加入队列;只有队列中没有等待线程,才会尝试CAS抢锁——这就是公平锁“先到先得”的核心实现。

3. 核心方法:unlock()(释放锁)

ReentrantLock的unlock()方法,无论公平锁还是非公平锁,逻辑一致,都是调用Sync的release(1)方法,再间接调用AQS的release(1)方法。

// ReentrantLock的unlock方法public void unlock() {    sync.release(1);}// Sync类(继承AQS)的release方法protected final boolean tryRelease(int releases) {    // 1. 重入次数减1(releases=1)    int c = getState() - releases;    // 2. 只有锁持有者才能释放锁,否则抛出异常    if (Thread.currentThread() != getExclusiveOwnerThread())        throw new IllegalMonitorStateException();    boolean free = false;    // 3. 如果重入次数为0,说明锁已完全释放,标记锁为空闲    if (c == 0) {        free = true;        setExclusiveOwnerThread(null); // 清空锁持有者    }    // 4. 更新state(即使未完全释放,也要更新重入次数)    setState(c);    return free;}

核心逻辑:可重入锁的释放是“渐进式”的——每次unlock(),state减1,只有当state减到0时,才会真正释放锁(清空锁持有者),并唤醒队列中的等待线程;如果state>0,说明线程还在重入,锁未完全释放。

注意:ReentrantLock的unlock()必须手动调用,且必须放在finally块中,否则会导致锁泄漏(线程异常退出,锁未释放,其他线程无法获取锁)。

4. 关键特性:可重入性、中断、Condition

  • 可重入性:通过tryAcquire()中“判断当前线程是否是锁持有者”实现,state记录重入次数,每次获取锁state+1,释放锁state-1,state=0时锁释放;

  • 可中断:ReentrantLock提供lockInterruptibly()方法,允许线程在等待锁时被中断(synchronized不支持中断);

  • Condition条件等待:通过newCondition()方法创建Condition对象,实现“等待-通知”机制,支持多条件等待(synchronized只能通过wait()/notify()实现单一条件等待)。

四、ReentrantLock与synchronized深度对比

ReentrantLock和synchronized都是Java中实现线程同步的核心方式,二者底层原理不同、特性不同,适用场景也不同。

1. 核心对比表

对比维度
ReentrantLock
synchronized
底层实现
基于AQS框架
(Java代码实现),依赖CAS和CLH队列
JVM内置锁(C++实现),
基于对象头的
Monitor管程模型
锁类型
可重入锁,支持公平锁、
非公平锁(默认非公平)
可重入锁,仅支持
非公平锁(JDK1.8优化
后,性能接近ReentrantLock)
锁释放
手动释放,必须在
finally块中调用unlock(),否则锁泄漏
自动释放,线程退出
同步块/方法时,JVM自
动释放锁(无需手动操作)
中断支持
支持中断(lockInterruptibly()方法),等待锁的线程可被中断
不支持中断,等待锁的
线程会一直阻塞,无法
被中断
条件等待
支持多条件等待(Condition),可实现精准的线程唤醒
仅支持单一条件
等待(wait()/notify()/
notifyAll()),唤醒时
随机唤醒一个线程
锁粒度
细粒度,可灵活控制
锁的范围(如代码块锁)
粗粒度(早期),JDK1.8
后优化,支持偏向锁、
轻量级锁,粒度可细化
性能
高并发场景下性能
更优,无锁竞争时略逊于synchronized(有CAS开销)
JDK1.8前性能较差,
JDK1.8后(偏向锁、
轻量级锁)性能接近ReentrantLock,
无锁竞争时性能更优
公平性
可选择公平/非公平锁,
公平锁保证线程排队顺序
仅非公平锁,无法保证
线程竞争顺序,可能
出现线程饥饿
使用复杂度
较复杂,需手动创建锁
对象、手动释放,注意异常处理
简单,只需加关键字
(方法/代码块),
无需手动管理锁的生命周期
适用场景
高并发、需要灵活控制锁(中断、公平锁、多条件等待)的场景
低并发、简单同步场景,
或不需要灵活控制
锁的场景(优先选择,
开发效率高)

2. 关键差异补充

(1)锁的升级机制不同

synchronized在JDK1.8后引入了“锁升级”机制(无锁 → 偏向锁 → 轻量级锁 → 重量级锁),根据线程竞争情况动态升级锁的级别,减少锁的开销;而ReentrantLock没有锁升级机制,始终基于AQS的CLH队列和CAS操作,锁的级别固定(独占锁)。

(2)异常处理不同

synchronized遇到异常时,JVM会自动释放锁,不会导致锁泄漏;而ReentrantLock如果在lock()后、unlock()前发生异常,会导致锁无法释放(锁泄漏),因此必须将unlock()放在finally块中,确保无论是否异常,锁都能释放。

(3)线程唤醒机制不同

synchronized的notify()方法会随机唤醒一个等待的线程,notifyAll()会唤醒所有等待的线程,无法精准唤醒指定线程;而ReentrantLock的Condition可以实现精准唤醒(signal()唤醒一个指定条件的线程,signalAll()唤醒所有指定条件的线程),灵活性更高。

3. 实战选择建议

  • 如果是简单的同步场景(如单例模式、简单方法同步),优先选择synchronized——开发效率高,无需手动管理锁,JDK优化后性能足够;

  • 如果是高并发场景,或需要灵活控制锁(如公平锁、中断、多条件等待),选择ReentrantLock——灵活性高,性能更稳定;

  • 如果是分布式场景,无论是ReentrantLock还是synchronized都不适用(仅支持单机同步),需使用分布式锁(如Redisson、ZooKeeper实现)。

五、总结:核心要点梳理

  1. AQS核心:一个state状态变量(控制锁状态)、一个CLH双向队列(管理等待线程)、一套模板方法(封装通用流程),是并发工具的底层基石;
  2. ReentrantLock核心:基于AQS实现,内部有NonfairSync(非公平锁)和FairSync(公平锁)两个子类,支持可重入、中断、多条件等待,需手动释放锁;
  3. 与synchronized差异:底层实现不同、锁类型不同、释放方式不同、灵活性不同,选择时需结合场景(简单场景用synchronized,复杂场景用ReentrantLock);
  4. 重点:AQS的核心组件、ReentrantLock的公平/非公平锁实现、可重入性原理、二者的核心差异及适用场景。

懂AQS,才能看懂Java并发工具的底层逻辑;懂ReentrantLock与synchronized的差异,才能在生产中选择最合适的同步方式。并发编程的核心不是“加锁”,而是“合理控制线程竞争”——AQS提供了竞争的框架,ReentrantLock和synchronized提供了具体的竞争实现,掌握它们,才能搞定高并发场景。