乐于分享
好东西不私藏

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

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的内部类,核心属性如下:

staticfinalclassNode {// 共享模式标记staticfinalNodeSHARED=newNode();// 独占模式标记staticfinalNodeEXCLUSIVE=null;// 线程被取消(如超时、中断)staticfinalintCANCELLED=1;// 当前线程的后继线程需要被唤醒staticfinalintSIGNAL= -1;// 线程正在等待条件(Condition)staticfinalintCONDITION= -2;// 共享模式下,状态需要传播(如Semaphore)staticfinalintPROPAGATE= -3;// 节点的等待状态(上述4种状态之一)volatileint 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 (;;) {Nodet= tail;// 队列为空时,初始化头节点(哨兵节点)if (t == null) {if (compareAndSetHead(newNode()))                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方法核心流程(源码简化版):

publicfinalvoidacquire(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的核心内部类,封装了线程和等待状态,源码关键细节如下(重点解析核心属性和方法):

staticfinalclassNode {// 共享模式标记(静态常量,所有共享节点共用)staticfinalNodeSHARED=newNode();// 独占模式标记(null表示独占)staticfinalNodeEXCLUSIVE=null;// 节点状态:取消(线程被中断或超时,不再参与竞争)staticfinalintCANCELLED=1;// 节点状态:信号(当前节点的后继节点需要被唤醒)staticfinalintSIGNAL= -1;// 节点状态:条件等待(线程在条件队列中等待)staticfinalintCONDITION= -2;// 节点状态:传播(共享模式下,唤醒后续节点)staticfinalintPROPAGATE= -3;// 节点的等待状态,volatile修饰,确保多线程可见性volatileint waitStatus;// 前驱节点,volatile修饰,用于双向链表关联volatile Node prev;// 后继节点,volatile修饰,用于双向链表关联volatile Node next;// 当前节点对应的线程,volatile修饰,关联阻塞的线程volatile Thread thread;// 条件队列中的后继节点(单向链表,用于Condition)    Node nextWaiter;// 判断当前节点是否为共享模式finalbooleanisShared() {return nextWaiter == SHARED;    }// 获取前驱节点,若为null则抛出异常(用于校验)final Node predecessor()throws NullPointerException {Nodep= prev;if (p == null)thrownewNullPointerException();elsereturn 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是独占模式下获取资源的核心模板方法,封装了“尝试获取-入队阻塞-唤醒重试”的完整逻辑,源码如下:

publicfinalvoidacquire(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)根据自身逻辑实现,核心目的是“尝试获取资源”,返回布尔值表示是否成功:

protectedbooleantryAcquire(int arg) {thrownewUnsupportedOperationException();}

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

protectedfinalbooleantryAcquire(int acquires) {finalThreadcurrent= Thread.currentThread();intc= getState();// 1. 若state为0(无锁),尝试CAS更新为acquires(1)if (c == 0) {if (compareAndSetState(0, acquires)) {            setExclusiveOwnerThread(current);returntrue;        }    }// 2. 若当前线程是锁的持有者(重入),state递增elseif (current == getExclusiveOwnerThread()) {intnextc= c + acquires;if (nextc < 0// 溢出判断thrownewError("Maximum lock count exceeded");        setState(nextc);returntrue;    }// 3. 其他情况,获取失败returnfalse;}

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

private Node addWaiter(Node mode) {// 1. 创建新Node节点,关联当前线程和模式(独占/共享)Nodenode=newNode(Thread.currentThread(), mode);// 2. 尝试快速入队(直接将节点加入尾部)Nodepred= 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):节点自旋等待获取资源

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

finalbooleanacquireQueued(final Node node, int arg) {booleanfailed=true;try {booleaninterrupted=false;// 自旋循环,直到获取资源或被中断for (;;) {// 获取当前节点的前驱节点finalNodep= 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是独占模式下释放资源的核心模板方法,释放资源后,唤醒队列头部的后继节点,源码如下:

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

关键解析:

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

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

ReentrantLock的tryRelease实现示例:

protectedfinalbooleantryRelease(int releases) {intc= getState() - releases;// 只有锁的持有者才能释放锁if (Thread.currentThread() != getExclusiveOwnerThread())thrownewIllegalMonitorStateException();booleanfree=false;// 当state=0时,释放锁,设置独占线程为nullif (c == 0) {        free = true;        setExclusiveOwnerThread(null);    }    setState(c);return free;}

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

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

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

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

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

privatevoiddoAcquireShared(int arg) {// 加入共享模式节点finalNodenode= addWaiter(Node.SHARED);booleanfailed=true;try {booleaninterrupted=false;for (;;) {finalNodep= node.predecessor();if (p == head) {intr= 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):共享式释放资源

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

publicfinalbooleanreleaseShared(int arg) {// 1. 尝试释放共享资源,tryReleaseShared由子类实现if (tryReleaseShared(arg)) {// 2. 唤醒后续节点,并传播唤醒        doReleaseShared();returntrue;    }returnfalse;}

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

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

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

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

publicfinalvoidawait()throws InterruptedException {if (Thread.interrupted())thrownewInterruptedException();// 1. 将当前线程加入条件队列Nodenode= addConditionWaiter();// 2. 释放当前持有的独占锁intsavedState= fullyRelease(node);intinterruptMode=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()方法用于唤醒条件队列的头节点,将其移到同步队列,等待获取资源,源码如下:

publicfinalvoidsignal() {// 1. 只有锁的持有者才能调用signal()if (!isHeldExclusively())thrownewIllegalMonitorStateException();// 2. 获取条件队列的头节点Nodefirst= 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实现自定义独占可重入锁 */publicclassCustomReentrantLockimplementsLock {// 自定义同步器,继承AQS,实现核心逻辑privatefinalSyncsync=newSync();// 内部类:同步器实现privatestaticclassSyncextendsAbstractQueuedSynchronizer {// 尝试获取独占锁(重写AQS的tryAcquire)@OverrideprotectedbooleantryAcquire(int arg) {finalThreadcurrent= Thread.currentThread();intstate= getState();// 1. 无锁状态,尝试CAS获取锁if (state == 0) {if (compareAndSetState(0, arg)) {// 设置当前线程为独占锁持有者                    setExclusiveOwnerThread(current);returntrue;                }            }// 2. 重入:当前线程是锁的持有者,state递增elseif (current == getExclusiveOwnerThread()) {intnextState= state + arg;if (nextState < 0) {thrownewError("锁重入次数溢出");                }                setState(nextState);returntrue;            }// 3. 其他线程持有锁,获取失败returnfalse;        }// 尝试释放独占锁(重写AQS的tryRelease)@OverrideprotectedbooleantryRelease(int arg) {// 只有锁的持有者才能释放锁if (Thread.currentThread() != getExclusiveOwnerThread()) {thrownewIllegalMonitorStateException();            }intstate= getState() - arg;booleanfree=false;// 当state=0时,释放锁,清空独占线程if (state == 0) {                free = true;                setExclusiveOwnerThread(null);            }            setState(state);return free;        }// 判断当前线程是否持有锁@OverrideprotectedbooleanisHeldExclusively() {return getExclusiveOwnerThread() == Thread.currentThread();        }    }// 实现Lock接口的lock方法(调用AQS的acquire)@Overridepublicvoidlock() {        sync.acquire(1);    }// 实现Lock接口的unlock方法(调用AQS的release)@Overridepublicvoidunlock() {        sync.release(1);    }// 以下方法简化实现,仅满足示例需求@OverridepublicvoidlockInterruptibly()throws InterruptedException {        sync.acquireInterruptibly(1);    }@OverridepublicbooleantryLock() {return sync.tryAcquire(1);    }@OverridepublicbooleantryLock(long time, java.util.concurrent.TimeUnit unit)throws InterruptedException {return sync.tryAcquireNanos(1, unit.toNanos(time));    }@Overridepublic java.util.concurrent.locks.Condition newCondition() {thrownewUnsupportedOperationException();    }}

4.1.3 测试代码与结果

/** * 测试自定义可重入锁 */publicclassCustomReentrantLockTest {privatestaticfinalCustomReentrantLocklock=newCustomReentrantLock();privatestaticintcount=0;publicstaticvoidmain(String[] args)throws InterruptedException {// 创建10个线程,每个线程执行1000次自增        Thread[] threads = newThread[10];for (inti=0; i < 10; i++) {            threads[i] = newThread(() -> {                lock.lock();try {for (intj=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实现自定义倒计时器 */publicclassCustomCountDownLatch {// 自定义同步器,继承AQSprivatefinal Sync sync;// 构造方法:初始化倒计时次数publicCustomCountDownLatch(int count) {if (count < 0) {thrownewIllegalArgumentException("count不能为负数");        }this.sync = newSync(count);    }// 内部类:同步器实现(共享模式)privatestaticclassSyncextendsAbstractQueuedSynchronizer {// 初始化state为倒计时次数        Sync(int count) {            setState(count);        }// 尝试获取共享资源(await方法调用)@OverrideprotectedinttryAcquireShared(int arg) {// state=0时,获取成功(返回1);否则获取失败(返回-1)return getState() == 0 ? 1 : -1;        }// 尝试释放共享资源(countDown方法调用)@OverrideprotectedbooleantryReleaseShared(int arg) {// 自旋CAS更新state,确保线程安全for (;;) {intstate= getState();if (state == 0) {returnfalse// 倒计时已结束,无需释放                }intnextState= state - 1;// CAS更新state为nextStateif (compareAndSetState(state, nextState)) {// 当state=0时,释放成功,唤醒所有等待线程return nextState == 0;                }            }        }    }// 倒计时方法:调用AQS的releaseSharedpublicvoidcountDown() {        sync.releaseShared(1);    }// 等待方法:调用AQS的acquireSharedpublicvoidawait()throws InterruptedException {        sync.acquireSharedInterruptibly(1);    }// 获取当前剩余倒计时次数publicintgetCount() {return sync.getState();    }}

4.2.3 测试代码与结果

/** * 测试自定义倒计时器 */publicclassCustomCountDownLatchTest {publicstaticvoidmain(String[] args)throws InterruptedException {// 初始化倒计时器,次数为3CustomCountDownLatchlatch=newCustomCountDownLatch(3);// 创建3个线程,每个线程执行完成后调用countDownfor (inti=0; i < 3; i++) {intfinalI= i;newThread(() -> {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同步组件提供底层支撑。掌握其原理与源码,既能理解现有同步组件的工作机制,也能自定义符合业务需求的同步工具,结合性能调优技巧,可在高并发场景下提升程序效率与安全性。