Java AQS实现原理、源码解析与实战调优全指南

 

核心词:AQS、AbstractQueuedSynchronizer、同步器、源码解析、实战案例、性能调优、锁机制

一、AQS核心认知:什么是AQS及核心作用

1.1 AQS的定义与设计初衷

AQS(AbstractQueuedSynchronizer,抽象队列同步器)是Java并发编程中核心同步框架,位于java.util.concurrent.locks包下,是几乎所有JUC同步组件(如ReentrantLock、CountDownLatch、Semaphore、CyclicBarrier等)的底层实现基础。

AQS的设计初衷是封装同步组件的通用逻辑,解决同步场景中“资源竞争、线程排队、唤醒机制”的重复实现问题,让开发者只需关注“资源状态定义”和“同步逻辑细节”,无需从零实现线程排队、阻塞与唤醒等复杂逻辑,大幅降低并发组件的开发难度。

简单来说,AQS就像一个“同步模板”,定义了一套通用的同步骨架,不同的同步组件(锁、计数器等)只需基于AQS扩展,实现自身的资源竞争规则即可。

1.2 AQS的核心设计思想

AQS的核心设计围绕“状态管理”和“队列管理”两大核心,采用“模板方法模式”设计,核心思路如下:

  1. 状态管理:通过一个volatile修饰的int类型变量state,表示同步资源的状态(如锁的持有状态、计数器的剩余次数等),确保多线程下状态的可见性和原子性。

  2. 队列管理:维护一个FIFO(先进先出)的双向同步队列,用于存放因竞争资源失败而阻塞的线程,实现线程的有序排队和唤醒。

  3. 模板方法:AQS定义了一系列核心模板方法(如acquire、release等),封装了线程排队、阻塞、唤醒的通用逻辑;同时暴露了一组抽象方法(如tryAcquire、tryRelease等),由子类(具体同步组件)实现,定义自身的资源竞争规则。

核心优势:解耦通用同步逻辑与具体业务逻辑,提升代码复用性和可维护性;统一线程排队和唤醒机制,确保并发场景下的线程安全。

1.3 AQS的核心组件与结构

AQS的核心组件由“状态变量、同步队列、条件队列、线程唤醒机制”四部分组成,各组件协同工作,实现同步功能:

  • 状态变量(state):volatile int类型,核心核心状态标识,由子类根据自身场景定义含义(如ReentrantLock中,state=0表示无锁,state>0表示锁被持有,数值表示重入次数)。

  • 同步队列(CLH队列):双向链表实现的FIFO队列,存放竞争资源失败的阻塞线程,每个节点对应一个线程,包含线程引用、等待状态、前驱和后继节点。

  • 条件队列(Condition Queue):由Condition接口实现,用于实现“线程等待-唤醒”的灵活机制(如ReentrantLock的Condition.await()/signal()),一个AQS可以对应多个条件队列。

  • 线程唤醒机制:基于Unsafe类的park()/unpark()方法实现线程的阻塞与唤醒,底层依赖操作系统的信号量机制,确保线程唤醒的高效性。

补充:CLH队列(Craig, Landin, and Hagersten队列)是一种基于双向链表的无锁队列,特点是“自旋+FIFO”,AQS对其进行了优化,加入了线程阻塞机制,避免自旋带来的CPU浪费。

二、AQS实现原理深度剖析

2.1 核心状态变量state的设计与操作

2.1.1 state的核心作用

state是AQS的核心状态载体,volatile修饰确保多线程间的可见性,其具体含义由子类定义,常见场景:

  • ReentrantLock(可重入锁):state=0表示无锁,state>0表示锁被持有,state值等于重入次数(如重入3次,state=3)。

  • Semaphore(信号量):state表示可用的许可数量,线程获取许可时state递减,释放许可时state递增。

  • CountDownLatch(倒计时器):state表示剩余倒计时次数,线程调用countDown()时state递减,state=0时唤醒所有等待线程。

2.1.2 state的原子操作方法

AQS提供了3个核心方法,用于对state进行原子操作(基于Unsafe类的CAS操作实现),确保多线程下状态修改的原子性:

  • getState():获取当前state的值,volatile保证可见性,无锁操作。

  • setState(int newState):设置state的值,仅在无竞争场景下使用(如独占锁释放时)。

  • compareAndSetState(int expect, int update):CAS操作,期望state为expect时,将其更新为update,返回布尔值表示更新是否成功,是多线程竞争state的核心方法。

示例:ReentrantLock中,线程获取锁时,会通过compareAndSetState(0, 1)尝试将state从0(无锁)更新为1(持有锁),更新成功则获取锁,失败则进入同步队列阻塞。

2.2 同步队列(CLH队列)的实现原理

2.2.1 队列结构设计

AQS的同步队列是一个双向链表,每个节点对应一个阻塞的线程,节点类(Node)是AQS的内部类,核心属性如下:

static final class Node {
    // 共享模式标记
    static final Node SHARED = new Node();
    // 独占模式标记
    static final Node EXCLUSIVE = null;

    // 线程被取消(如超时、中断)
    static final int CANCELLED =  1;
    // 当前线程的后继线程需要被唤醒
    static final int SIGNAL    = -1;
    // 线程正在等待条件(Condition)
    static final int CONDITION = -2;
    // 共享模式下,状态需要传播(如Semaphore)
    static final int PROPAGATE = -3;

    // 节点的等待状态(上述4种状态之一)
    volatile int waitStatus;
    // 前驱节点
    volatile Node prev;
    // 后继节点
    volatile Node next;
    // 当前节点对应的线程
    volatile Thread thread;
    // 条件队列中的后继节点(用于Condition)
    Node nextWaiter;

    // 省略构造方法和辅助方法
}

同步队列的核心结构:

  • 队列有两个核心指针:head(头节点)和tail(尾节点),均为volatile修饰,确保多线程下的可见性。

  • 头节点是“哨兵节点”(空节点),不对应任何线程,仅用于标记队列头部,简化队列操作。

  • 新线程竞争资源失败时,会被封装为Node节点,通过CAS操作加入队列尾部(尾插法),保证队列的FIFO特性。

2.2.2 队列的核心操作:入队与出队

1. 入队操作(enqueue)

当线程竞争资源失败(如CAS更新state失败),AQS会将该线程封装为Node节点,加入同步队列尾部,核心逻辑:

  1. 创建新Node节点,关联当前线程,初始等待状态为0。

  2. 通过CAS操作将tail指针指向新节点,同时将原tail节点的next指向新节点(双向链表关联)。

  3. 若CAS操作失败(多线程并发入队),则自旋重试,直到入队成功。

核心源码逻辑(简化版):

private Node enqueue(Node node) {
    for (;;) {
        Node t = tail;
        // 队列为空时,初始化头节点(哨兵节点)
        if (t == null) {
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            // 尾插法:将新节点的prev指向当前tail
            node.prev = t;
            // CAS更新tail为新节点
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

2. 出队操作(dequeue)

当持有资源的线程释放资源时,会唤醒队列头部的后继节点(头节点的next节点),该节点对应的线程会尝试获取资源,获取成功后,将头节点更新为自身,完成出队操作,核心逻辑:

  1. 唤醒头节点的后继节点(通过Unsafe.unpark()方法)。

  2. 被唤醒的线程尝试获取资源(调用tryAcquire方法)。

  3. 获取资源成功后,将当前节点设为新的头节点(哨兵节点),原头节点被GC回收,完成出队。

注意:出队操作无需CAS,因为只有头节点的后继节点能被唤醒,且同一时刻只有一个线程能获取资源,不存在并发竞争。

2.3 AQS的核心模板方法:独占模式与共享模式

AQS支持两种同步模式,对应不同的同步场景,核心模板方法分别针对两种模式设计,子类需根据自身需求实现对应抽象方法。

2.3.1 独占模式(Exclusive Mode)

独占模式:同一时刻,只有一个线程能持有资源(如ReentrantLock的独占锁),核心模板方法:

  • acquire(int arg):独占式获取资源,核心逻辑:尝试获取资源(tryAcquire)→ 失败则入队阻塞 → 被唤醒后再次尝试获取资源。

  • release(int arg):独占式释放资源,核心逻辑:尝试释放资源(tryRelease)→ 释放成功后,唤醒队列头部的后继节点。

acquire方法核心流程(源码简化版):

public final void acquire(int arg) {
    // 1. 尝试获取资源,tryAcquire由子类实现
    if (!tryAcquire(arg) &&
        // 2. 获取失败,入队并阻塞
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
        // 3. 若线程被中断,处理中断逻辑
        selfInterrupt();
    }
}

关键说明:

  • tryAcquire(int arg):抽象方法,由子类实现,定义独占式获取资源的规则(如ReentrantLock中,判断state是否为0,若为0则CAS更新为1,若为当前线程持有则state递增)。

  • addWaiter(Node mode):将当前线程封装为Node节点,加入同步队列,mode为EXCLUSIVE(独占模式)。

  • acquireQueued(Node node, int arg):让节点在队列中自旋等待,直到获取资源或被中断,期间会调用park()方法阻塞线程,减少CPU消耗。

2.3.2 共享模式(Shared Mode)

共享模式:同一时刻,多个线程可同时持有资源(如Semaphore、CountDownLatch),核心模板方法:

  • acquireShared(int arg):共享式获取资源,核心逻辑:尝试获取资源(tryAcquireShared)→ 失败则入队阻塞 → 被唤醒后再次尝试获取资源,并传播唤醒后续节点。

  • releaseShared(int arg):共享式释放资源,核心逻辑:尝试释放资源(tryReleaseShared)→ 释放成功后,唤醒队列头部的后继节点,并传播唤醒后续所有共享节点。

与独占模式的核心区别:

  • 共享模式下,线程获取资源成功后,会唤醒后续所有等待的共享节点(如Semaphore中,一个线程释放许可后,多个线程可同时获取许可)。

  • tryAcquireShared返回int值:>0表示获取资源成功,且剩余资源可用;=0表示获取资源成功,但无剩余资源;<0表示获取资源失败。

2.4 条件队列(Condition)的实现原理

Condition是AQS的扩展机制,用于实现“线程等待-唤醒”的灵活控制(类似Object的wait()/notify(),但更强大,支持多个条件队列),核心原理:

  • 每个Condition对应一个独立的单向链表队列(条件队列),存放调用await()方法阻塞的线程。

  • 当线程调用Condition.await()时,会释放当前持有的资源,将自身加入条件队列,然后阻塞;当其他线程调用Condition.signal()时,会将条件队列的头节点移到同步队列,等待获取资源。

核心操作流程:

  1. await():释放资源 → 将线程加入条件队列 → 阻塞线程 → 被唤醒后,移到同步队列,尝试获取资源。

  2. signal():将条件队列的头节点移到同步队列 → 唤醒该节点对应的线程,使其尝试获取资源。

补充:Condition的await()/signal()方法必须在持有锁(独占锁)的情况下调用,否则会抛出IllegalMonitorStateException异常,这与Object的wait()/notify()要求一致。

三、AQS源码深度解析(基于JDK 8)

3.1 核心内部类Node源码解析

Node是AQS的核心内部类,封装了线程和等待状态,源码关键细节如下(重点解析核心属性和方法):

static final class Node {
    // 共享模式标记(静态常量,所有共享节点共用)
    static final Node SHARED = new Node();
    // 独占模式标记(null表示独占)
    static final Node EXCLUSIVE = null;

    // 节点状态:取消(线程被中断或超时,不再参与竞争)
    static final int CANCELLED =  1;
    // 节点状态:信号(当前节点的后继节点需要被唤醒)
    static final int SIGNAL    = -1;
    // 节点状态:条件等待(线程在条件队列中等待)
    static final int CONDITION = -2;
    // 节点状态:传播(共享模式下,唤醒后续节点)
    static final int PROPAGATE = -3;

    // 节点的等待状态,volatile修饰,确保多线程可见性
    volatile int waitStatus;

    // 前驱节点,volatile修饰,用于双向链表关联
    volatile Node prev;

    // 后继节点,volatile修饰,用于双向链表关联
    volatile Node next;

    // 当前节点对应的线程,volatile修饰,关联阻塞的线程
    volatile Thread thread;

    // 条件队列中的后继节点(单向链表,用于Condition)
    Node nextWaiter;

    // 判断当前节点是否为共享模式
    final boolean isShared() {
        return nextWaiter == SHARED;
    }

    // 获取前驱节点,若为null则抛出异常(用于校验)
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }

    // 无参构造(用于头节点/哨兵节点)
    Node() {}

    // 用于同步队列的构造(关联线程和模式)
    Node(Thread thread, Node mode) {
        this.nextWaiter = mode;
        this.thread = thread;
    }

    // 用于条件队列的构造(关联线程和等待状态)
    Node(Thread thread, int waitStatus) {
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}

关键细节说明:

  • waitStatus的取值范围:-3(PROPAGATE)、-2(CONDITION)、-1(SIGNAL)、0(初始状态)、1(CANCELLED),不同状态对应不同的线程行为。

  • nextWaiter:在同步队列中,用于标记节点的模式(SHARED/EXCLUSIVE);在条件队列中,用于关联下一个等待节点(单向链表)。

  • predecessor()方法:用于获取前驱节点,确保队列操作的正确性,若前驱节点为null,说明队列结构异常,抛出空指针异常。

3.2 核心方法源码解析(独占模式)

3.2.1 acquire(int arg):独占式获取资源

acquire是独占模式下获取资源的核心模板方法,封装了“尝试获取-入队阻塞-唤醒重试”的完整逻辑,源码如下:

public final void acquire(int arg) {
    // 1. 尝试获取资源(tryAcquire由子类实现),获取成功则直接返回
    // 2. 若获取失败,调用addWaiter将线程封装为独占节点,加入同步队列
    // 3. 调用acquireQueued,让节点在队列中自旋等待,直到获取资源或被中断
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
        // 4. 若线程在等待过程中被中断,调用selfInterrupt()补全中断状态
        selfInterrupt();
    }
}

分步解析:

1. tryAcquire(int arg):抽象方法,子类实现

AQS中tryAcquire是抽象方法,未提供具体实现,由子类(如ReentrantLock)根据自身逻辑实现,核心目的是“尝试获取资源”,返回布尔值表示是否成功:

protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

示例(ReentrantLock的非公平锁实现):

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    // 1. 若state为0(无锁),尝试CAS更新为acquires(1)
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 2. 若当前线程是锁的持有者(重入),state递增
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0// 溢出判断
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    // 3. 其他情况,获取失败
    return false;
}

2. addWaiter(Node mode):封装线程为Node,加入同步队列

private Node addWaiter(Node mode) {
    // 1. 创建新Node节点,关联当前线程和模式(独占/共享)
    Node node = new Node(Thread.currentThread(), mode);
    // 2. 尝试快速入队(直接将节点加入尾部)
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        // CAS更新tail为新节点,成功则入队完成
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 3. 快速入队失败(多线程并发),调用enqueue自旋入队
    enqueue(node);
    return node;
}

关键:快速入队失败后,调用enqueue方法自旋重试,确保节点一定能加入队列,避免并发问题。

3. acquireQueued(Node node, int arg):节点自旋等待获取资源

该方法是线程阻塞和唤醒的核心,让节点在队列中自旋,直到获取资源或被中断,源码简化版:

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        // 自旋循环,直到获取资源或被中断
        for (;;) {
            // 获取当前节点的前驱节点
            final Node p = node.predecessor();
            // 若前驱节点是头节点,尝试获取资源(头节点是哨兵节点,其next节点优先获取资源)
            if (p == head && tryAcquire(arg)) {
                // 获取资源成功,将当前节点设为新的头节点(哨兵节点)
                setHead(node);
                p.next = null// 原头节点断开连接,便于GC回收
                failed = false;
                return interrupted;
            }
            // 若获取资源失败,判断是否需要阻塞线程
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt()) {
                // 线程被中断,标记interrupted为true
                interrupted = true;
            }
        }
    } finally {
        // 若获取资源失败,取消当前节点(标记为CANCELLED)
        if (failed)
            cancelAcquire(node);
    }
}

关键方法解析:

  • shouldParkAfterFailedAcquire(Node pred, Node node):判断当前节点是否需要阻塞,核心逻辑是将前驱节点的状态设为SIGNAL(表示当前节点需要被唤醒),若前驱节点状态为CANCELLED,则删除该前驱节点,整理队列。

  • parkAndCheckInterrupt():调用Unsafe.park()方法阻塞当前线程,直到被unpark()唤醒,唤醒后返回线程的中断状态,并清除中断标记。

3.2.2 release(int arg):独占式释放资源

release是独占模式下释放资源的核心模板方法,释放资源后,唤醒队列头部的后继节点,源码如下:

public final boolean release(int arg) {
    // 1. 尝试释放资源(tryRelease由子类实现)
    if (tryRelease(arg)) {
        // 2. 释放成功,获取头节点
        Node h = head;
        // 3. 若头节点不为null且状态不为0,唤醒后继节点
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

关键解析:

  • tryRelease(int arg):抽象方法,子类实现,核心逻辑是释放资源(如ReentrantLock中,state递减,当state=0时,释放锁,设置独占线程为null)。

  • unparkSuccessor(Node h):唤醒头节点的后继节点,核心逻辑是找到头节点的有效后继节点(状态不为CANCELLED),调用Unsafe.unpark()方法唤醒该节点对应的线程。

ReentrantLock的tryRelease实现示例:

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    // 只有锁的持有者才能释放锁
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // 当state=0时,释放锁,设置独占线程为null
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

3.3 核心方法源码解析(共享模式)

3.3.1 acquireShared(int arg):共享式获取资源

共享模式下获取资源,支持多个线程同时获取,核心逻辑与独占模式类似,但多了“资源传播”的步骤,源码如下:

public final void acquireShared(int arg) {
    // 1. 尝试获取共享资源,tryAcquireShared由子类实现
    // 返回值>0:获取成功,有剩余资源;=0:获取成功,无剩余资源;&lt;0:获取失败
    if (tryAcquireShared(arg) < 0)
        // 2. 获取失败,入队阻塞,并传播唤醒后续节点
        doAcquireShared(arg);
}

关键方法doAcquireShared(arg):与acquireQueued类似,但在获取资源成功后,会唤醒后续所有共享节点,实现资源的传播唤醒,核心逻辑(简化版):

private void doAcquireShared(int arg) {
    // 加入共享模式节点
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    // 获取资源成功,更新头节点,并传播唤醒后续节点
                    setHeadAndPropagate(node, r);
                    p.next = null// 原头节点GC回收
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            // 失败则阻塞,逻辑与独占模式一致
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt()) {
                interrupted = true;
            }
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

3.3.2 releaseShared(int arg):共享式释放资源

共享模式下释放资源,释放后会唤醒后续所有共享节点,确保资源能被多个线程获取,源码如下:

public final boolean releaseShared(int arg) {
    // 1. 尝试释放共享资源,tryReleaseShared由子类实现
    if (tryReleaseShared(arg)) {
        // 2. 唤醒后续节点,并传播唤醒
        doReleaseShared();
        return true;
    }
    return false;
}

关键方法doReleaseShared():唤醒头节点的后继节点,并传播唤醒后续所有共享节点,避免遗漏等待线程,核心逻辑是通过自旋确保唤醒操作的原子性,应对多线程并发释放资源的场景。

3.4 条件队列核心方法源码解析

3.4.1 await():线程进入条件队列等待

Condition的await()方法必须在持有独占锁的情况下调用,核心逻辑是“释放锁→加入条件队列→阻塞线程→被唤醒后加入同步队列”,源码简化版:

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    // 1. 将当前线程加入条件队列
    Node node = addConditionWaiter();
    // 2. 释放当前持有的独占锁
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    // 3. 判断当前节点是否在同步队列中,不在则阻塞
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        // 4. 检查线程是否被中断,若被中断则标记中断模式
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    // 5. 被唤醒后,加入同步队列,尝试获取锁
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    // 6. 清理条件队列中的无效节点(CANCELLED状态)
    if (node.nextWaiter != null)
        unlinkCancelledWaiters();
    // 7. 处理中断
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

3.4.2 signal():唤醒条件队列中的线程

signal()方法用于唤醒条件队列的头节点,将其移到同步队列,等待获取资源,源码如下:

public final void signal() {
    // 1. 只有锁的持有者才能调用signal()
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    // 2. 获取条件队列的头节点
    Node first = firstWaiter;
    if (first != null)
        // 3. 唤醒头节点,移到同步队列
        doSignal(first);
}

关键方法doSignal(Node first):将条件队列的头节点移到同步队列,核心逻辑是通过CAS操作更新条件队列的头节点,然后将该节点移到同步队列,唤醒对应的线程。

四、AQS实战案例:基于AQS自定义同步组件

掌握AQS的核心原理后,我们可以基于AQS自定义同步组件,实现符合业务需求的同步逻辑。本节将实现两个实战案例:自定义独占锁(类似ReentrantLock)、自定义计数器(类似CountDownLatch),帮助理解AQS的实际应用。

4.1 实战案例1:自定义独占可重入锁(CustomReentrantLock)

4.1.1 需求分析

实现一个简单的独占可重入锁,具备以下功能:

  • 独占模式:同一时刻只有一个线程能持有锁。

  • 可重入:持有锁的线程可多次获取锁,释放时需对应次数释放。

  • 支持lock()(获取锁,阻塞)和unlock()(释放锁)方法。

4.1.2 实现代码

import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Lock;

/**
 * 基于AQS实现自定义独占可重入锁
 */
public class CustomReentrantLock implements Lock {

    // 自定义同步器,继承AQS,实现核心逻辑
    private final Sync sync = new Sync();

    // 内部类:同步器实现
    private static class Sync extends AbstractQueuedSynchronizer {

        // 尝试获取独占锁(重写AQS的tryAcquire)
        @Override
        protected boolean tryAcquire(int arg) {
            final Thread current = Thread.currentThread();
            int state = getState();

            // 1. 无锁状态,尝试CAS获取锁
            if (state == 0) {
                if (compareAndSetState(0, arg)) {
                    // 设置当前线程为独占锁持有者
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 2. 重入:当前线程是锁的持有者,state递增
            else if (current == getExclusiveOwnerThread()) {
                int nextState = state + arg;
                if (nextState < 0) {
                    throw new Error("锁重入次数溢出");
                }
                setState(nextState);
                return true;
            }
            // 3. 其他线程持有锁,获取失败
            return false;
        }

        // 尝试释放独占锁(重写AQS的tryRelease)
        @Override
        protected boolean tryRelease(int arg) {
            // 只有锁的持有者才能释放锁
            if (Thread.currentThread() != getExclusiveOwnerThread()) {
                throw new IllegalMonitorStateException();
            }

            int state = getState() - arg;
            boolean free = false;
            // 当state=0时,释放锁,清空独占线程
            if (state == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(state);
            return free;
        }

        // 判断当前线程是否持有锁
        @Override
        protected boolean isHeldExclusively() {
            return getExclusiveOwnerThread() == Thread.currentThread();
        }
    }

    // 实现Lock接口的lock方法(调用AQS的acquire)
    @Override
    public void lock() {
        sync.acquire(1);
    }

    // 实现Lock接口的unlock方法(调用AQS的release)
    @Override
    public void unlock() {
        sync.release(1);
    }

    // 以下方法简化实现,仅满足示例需求
    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    @Override
    public boolean tryLock(long time, java.util.concurrent.TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(time));
    }

    @Override
    public java.util.concurrent.locks.Condition newCondition() {
        throw new UnsupportedOperationException();
    }
}

4.1.3 测试代码与结果

/**
 * 测试自定义可重入锁
 */
public class CustomReentrantLockTest {
    private static final CustomReentrantLock lock = new CustomReentrantLock();
    private static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        // 创建10个线程,每个线程执行1000次自增
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(() -> {
                lock.lock();
                try {
                    for (int j = 0; j < 1000; j++) {
                        count++;
                    }
                } finally {
                    // 必须在finally中释放锁,避免死锁
                    lock.unlock();
                }
            });
            threads[i].start();
        }

        // 等待所有线程执行完成
        for (Thread thread : threads) {
            thread.join();
        }

        // 输出结果,预期为10000
        System.out.println("最终count值:" + count);
    }
}

测试结果:最终count值:10000,说明自定义锁能保证线程安全,可重入功能正常。

4.2 实战案例2:自定义倒计时器(CustomCountDownLatch)

4.2.1 需求分析

实现一个简单的倒计时器,具备以下功能:

  • 初始化时指定倒计时次数(state初始值)。

  • countDown()方法:倒计时次数减1,当次数为0时,唤醒所有等待线程。

  • await()方法:线程阻塞,直到倒计时次数为0。

4.2.2 实现代码

import java.util.concurrent.locks.AbstractQueuedSynchronizer;

/**
 * 基于AQS实现自定义倒计时器
 */
public class CustomCountDownLatch {

    // 自定义同步器,继承AQS
    private final Sync sync;

    // 构造方法:初始化倒计时次数
    public CustomCountDownLatch(int count) {
        if (count < 0) {
            throw new IllegalArgumentException("count不能为负数");
        }
        this.sync = new Sync(count);
    }

    // 内部类:同步器实现(共享模式)
    private static class Sync extends AbstractQueuedSynchronizer {

        // 初始化state为倒计时次数
        Sync(int count) {
            setState(count);
        }

        // 尝试获取共享资源(await方法调用)
        @Override
        protected int tryAcquireShared(int arg) {
            // state=0时,获取成功(返回1);否则获取失败(返回-1)
            return getState() == 0 ? 1 : -1;
        }

        // 尝试释放共享资源(countDown方法调用)
        @Override
        protected boolean tryReleaseShared(int arg) {
            // 自旋CAS更新state,确保线程安全
            for (;;) {
                int state = getState();
                if (state == 0) {
                    return false// 倒计时已结束,无需释放
                }
                int nextState = state - 1;
                // CAS更新state为nextState
                if (compareAndSetState(state, nextState)) {
                    // 当state=0时,释放成功,唤醒所有等待线程
                    return nextState == 0;
                }
            }
        }
    }

    // 倒计时方法:调用AQS的releaseShared
    public void countDown() {
        sync.releaseShared(1);
    }

    // 等待方法:调用AQS的acquireShared
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    // 获取当前剩余倒计时次数
    public int getCount() {
        return sync.getState();
    }
}

4.2.3 测试代码与结果

/**
 * 测试自定义倒计时器
 */
public class CustomCountDownLatchTest {
    public static void main(String[] args) throws InterruptedException {
        // 初始化倒计时器,次数为3
        CustomCountDownLatch latch = new CustomCountDownLatch(3);

        // 创建3个线程,每个线程执行完成后调用countDown
        for (int i = 0; i < 3; i++) {
            int finalI = i;
            new Thread(() -> {
                try {
                    // 模拟业务逻辑执行
                    Thread.sleep(1000);
                    System.out.println("线程" + finalI + "执行完成,倒计时减1");
                    latch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }

        System.out.println("主线程等待倒计时结束...");
        // 主线程阻塞,直到倒计时为0
        latch.await();
        System.out.println("倒计时结束,主线程继续执行");
    }
}

测试结果:

主线程等待倒计时结束...
线程0执行完成,倒计时减1
线程1执行完成,倒计时减1
线程2执行完成,倒计时减1
倒计时结束,主线程继续执行

结果符合预期,说明自定义倒计时器能正常工作,实现了“等待-倒计时-唤醒”的逻辑。

五、AQS性能调优技巧

AQS作为JUC的底层核心,其性能直接影响并发程序的运行效率。在高并发场景下,不合理的使用或配置会导致AQS出现性能瓶颈,本节将结合实际场景,分享AQS的性能调优技巧。

5.1 核心调优方向:减少锁竞争

AQS的性能瓶颈主要来自“线程竞争资源导致的阻塞与唤醒”,减少锁竞争是最核心的调优方向,具体技巧:

5.1.1 选择合适的同步模式

  • 独占模式(如ReentrantLock):适用于“同一时刻只有一个线程操作资源”的场景(如单例、资源独占访问),但竞争激烈时,线程阻塞唤醒开销大。

  • 共享模式(如Semaphore):适用于“多个线程可同时访问资源”的场景(如连接池、限流),能有效提高并发度,减少线程阻塞。

调优建议:根据业务场景选择同步模式,避免滥用独占锁,能用共享模式的场景优先使用共享模式。

5.1.2 减小锁粒度

锁粒度越小,线程竞争的概率越低,AQS的阻塞唤醒开销越小。核心思路:将大锁拆分为多个小锁,让不同线程竞争不同的锁,减少并发冲突。

示例:ConcurrentHashMap的分段锁机制,将整个Map拆分为多个Segment,每个Segment对应一个锁,线程操作不同Segment时无需竞争同一把锁,大幅提升并发性能。

5.1.3 避免不必要的锁持有

线程持有锁的时间越长,其他线程等待的时间越长,竞争越激烈。调优建议:

  • 锁仅包裹“需要同步的核心代码”,避免将无关代码(如IO操作、耗时计算)放入锁范围内。

  • 尽量使用tryLock()尝试获取锁,避免线程长时间阻塞(若获取锁失败,可执行其他逻辑,而非一直等待)。

5.2 针对AQS内部机制的调优

5.2.1 优化同步队列的性能

AQS的同步队列是线程排队的核心,队列操作的效率直接影响AQS性能,调优技巧:

  • 避免频繁创建Node节点:在高并发场景下,线程频繁竞争失败会导致大量Node节点创建,增加GC压力。可通过“线程池复用线程”减少Node节点的创建(线程复用后,Node节点可重复使用)。

  • 减少队列自旋次数:AQS的acquireQueued方法中,线程会自旋尝试获取资源,自旋次数过多会消耗CPU。可通过合理设置锁的公平性(非公平锁比公平锁自旋次数少),减少自旋开销。

5.2.2 合理设置锁的公平性

AQS支持公平锁和非公平锁两种模式,不同模式的性能差异较大:

  • 公平锁:线程按FIFO顺序获取资源,避免线程饥饿,但会增加队列操作的开销(线程唤醒后需再次检查是否为队列头部),性能略低。

  • 非公平锁:线程获取资源时,会先尝试CAS获取锁,无需排队,减少队列操作开销,性能更高,但可能导致线程饥饿(个别线程长时间无法获取锁)。

调优建议:高并发场景下,优先使用非公平锁,牺牲少量公平性换取更高的并发性能;若业务要求必须公平(如避免线程饥饿),则使用公平锁。

六、总结

AQS是Java并发编程的核心同步骨架,以状态变量state和CLH同步队列为核心,通过模板方法模式封装通用同步逻辑,支持独占和共享两种模式,为JUC同步组件提供底层支撑。掌握其原理与源码,既能理解现有同步组件的工作机制,也能自定义符合业务需求的同步工具,结合性能调优技巧,可在高并发场景下提升程序效率与安全性。

 

Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注