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

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

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

AQS(AbstractQueuedSynchronizer),即抽象队列同步器,是Java并发包(java.util.concurrent.locks)的核心基石------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(保证原子性和可见性):

java 复制代码
// 获取当前同步状态(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节点核心结构:

java 复制代码
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()为例):

java 复制代码
// AQS的acquire方法(模板方法,定义了独占式获取锁的完整流程)
public final void acquire(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构造方法(公平/非公平锁选择)

java 复制代码
// 无参构造:默认非公平锁(性能更优,大多数场景首选)
public ReentrantLock() {
    sync = new NonfairSync();
}

// 有参构造:true=公平锁,false=非公平锁
public ReentrantLock(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()
java 复制代码
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    // 非公平锁获取锁的核心方法
    final void lock() {
        // 1. 尝试CAS抢锁:state从0→1,如果成功,标记当前线程为锁持有者
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            // 2. 抢锁失败,调用AQS的acquire(1),进入队列等待
            acquire(1);
    }

    // 重写AQS的tryAcquire方法(非公平锁的锁竞争逻辑)
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

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

(2)nonfairTryAcquire()(非公平锁尝试获取锁)
java 复制代码
// 非公平锁尝试获取锁的核心逻辑(可重入性实现)
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()
java 复制代码
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)方法。

java 复制代码
// 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提供了具体的竞争实现,掌握它们,才能搞定高并发场景。

相关推荐
小Y._7 天前
AQS同步器核心原理深度剖析
java·源码分析·juc·aqs
一叶飘零_sweeeet17 天前
深入理解 AQS:从架构到实现,解锁 Java 并发编程的核心密钥
java·aqs
Zzzzmo_17 天前
【JavaEE】多线程02—线程安全
java-ee·线程安全·synchronized
是码龙不是码农25 天前
synchronized 底层原理深度详解
java·synchronized
煎饼皮皮侠1 个月前
利用 AQS 构建一个自己的公平共享锁
java·aqs·公平共享锁
Maỿbe1 个月前
你了解synchronized吗
synchronized
庞轩px1 个月前
AQS(AbstractQueuedSynchronizer)源码深度解析:从CLH队列到ReentrantLock实现
java·并发编程·juc·aqs·reentrantlock
庞轩px1 个月前
Synchronized 与 ReentrantLock 深度对比
并发编程·synchronized·aqs··reentrantlock
C++chaofan1 个月前
RPC框架负载均衡机制深度解析
java·开发语言·负载均衡·juc·synchronized·