核心词: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的核心设计围绕“状态管理”和“队列管理”两大核心,采用“模板方法模式”设计,核心思路如下:
-
状态管理:通过一个volatile修饰的int类型变量
state,表示同步资源的状态(如锁的持有状态、计数器的剩余次数等),确保多线程下状态的可见性和原子性。 -
队列管理:维护一个FIFO(先进先出)的双向同步队列,用于存放因竞争资源失败而阻塞的线程,实现线程的有序排队和唤醒。
-
模板方法: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的设计与操作
state是AQS的核心状态载体,volatile修饰确保多线程间的可见性,其具体含义由子类定义,常见场景:
-
ReentrantLock(可重入锁):state=0表示无锁,state>0表示锁被持有,state值等于重入次数(如重入3次,state=3)。
-
Semaphore(信号量):state表示可用的许可数量,线程获取许可时state递减,释放许可时state递增。
-
CountDownLatch(倒计时器):state表示剩余倒计时次数,线程调用countDown()时state递减,state=0时唤醒所有等待线程。
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队列)的实现原理
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特性。
1. 入队操作(enqueue)
当线程竞争资源失败(如CAS更新state失败),AQS会将该线程封装为Node节点,加入同步队列尾部,核心逻辑:
-
创建新Node节点,关联当前线程,初始等待状态为0。
-
通过CAS操作将tail指针指向新节点,同时将原tail节点的next指向新节点(双向链表关联)。
-
若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节点),该节点对应的线程会尝试获取资源,获取成功后,将头节点更新为自身,完成出队操作,核心逻辑:
-
唤醒头节点的后继节点(通过Unsafe.unpark()方法)。
-
被唤醒的线程尝试获取资源(调用tryAcquire方法)。
-
获取资源成功后,将当前节点设为新的头节点(哨兵节点),原头节点被GC回收,完成出队。
注意:出队操作无需CAS,因为只有头节点的后继节点能被唤醒,且同一时刻只有一个线程能获取资源,不存在并发竞争。
2.3 AQS的核心模板方法:独占模式与共享模式
AQS支持两种同步模式,对应不同的同步场景,核心模板方法分别针对两种模式设计,子类需根据自身需求实现对应抽象方法。
独占模式:同一时刻,只有一个线程能持有资源(如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消耗。
共享模式:同一时刻,多个线程可同时持有资源(如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()时,会将条件队列的头节点移到同步队列,等待获取资源。
核心操作流程:
-
await():释放资源 → 将线程加入条件队列 → 阻塞线程 → 被唤醒后,移到同步队列,尝试获取资源。
-
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 核心方法源码解析(独占模式)
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()唤醒,唤醒后返回线程的中断状态,并清除中断标记。
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 核心方法源码解析(共享模式)
共享模式下获取资源,支持多个线程同时获取,核心逻辑与独占模式类似,但多了“资源传播”的步骤,源码如下:
public final void acquireShared(int arg) {
// 1. 尝试获取共享资源,tryAcquireShared由子类实现
// 返回值>0:获取成功,有剩余资源;=0:获取成功,无剩余资源;<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);
}
}
共享模式下释放资源,释放后会唤醒后续所有共享节点,确保资源能被多个线程获取,源码如下:
public final boolean releaseShared(int arg) {
// 1. 尝试释放共享资源,tryReleaseShared由子类实现
if (tryReleaseShared(arg)) {
// 2. 唤醒后续节点,并传播唤醒
doReleaseShared();
return true;
}
return false;
}
关键方法doReleaseShared():唤醒头节点的后继节点,并传播唤醒后续所有共享节点,避免遗漏等待线程,核心逻辑是通过自旋确保唤醒操作的原子性,应对多线程并发释放资源的场景。
3.4 条件队列核心方法源码解析
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);
}
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)
实现一个简单的独占可重入锁,具备以下功能:
-
独占模式:同一时刻只有一个线程能持有锁。
-
可重入:持有锁的线程可多次获取锁,释放时需对应次数释放。
-
支持lock()(获取锁,阻塞)和unlock()(释放锁)方法。
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();
}
}
/**
* 测试自定义可重入锁
*/
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)
实现一个简单的倒计时器,具备以下功能:
-
初始化时指定倒计时次数(state初始值)。
-
countDown()方法:倒计时次数减1,当次数为0时,唤醒所有等待线程。
-
await()方法:线程阻塞,直到倒计时次数为0。
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();
}
}
/**
* 测试自定义倒计时器
*/
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的性能瓶颈主要来自“线程竞争资源导致的阻塞与唤醒”,减少锁竞争是最核心的调优方向,具体技巧:
-
独占模式(如ReentrantLock):适用于“同一时刻只有一个线程操作资源”的场景(如单例、资源独占访问),但竞争激烈时,线程阻塞唤醒开销大。
-
共享模式(如Semaphore):适用于“多个线程可同时访问资源”的场景(如连接池、限流),能有效提高并发度,减少线程阻塞。
调优建议:根据业务场景选择同步模式,避免滥用独占锁,能用共享模式的场景优先使用共享模式。
锁粒度越小,线程竞争的概率越低,AQS的阻塞唤醒开销越小。核心思路:将大锁拆分为多个小锁,让不同线程竞争不同的锁,减少并发冲突。
示例:ConcurrentHashMap的分段锁机制,将整个Map拆分为多个Segment,每个Segment对应一个锁,线程操作不同Segment时无需竞争同一把锁,大幅提升并发性能。
线程持有锁的时间越长,其他线程等待的时间越长,竞争越激烈。调优建议:
-
锁仅包裹“需要同步的核心代码”,避免将无关代码(如IO操作、耗时计算)放入锁范围内。
-
尽量使用tryLock()尝试获取锁,避免线程长时间阻塞(若获取锁失败,可执行其他逻辑,而非一直等待)。
5.2 针对AQS内部机制的调优
AQS的同步队列是线程排队的核心,队列操作的效率直接影响AQS性能,调优技巧:
-
避免频繁创建Node节点:在高并发场景下,线程频繁竞争失败会导致大量Node节点创建,增加GC压力。可通过“线程池复用线程”减少Node节点的创建(线程复用后,Node节点可重复使用)。
-
减少队列自旋次数:AQS的acquireQueued方法中,线程会自旋尝试获取资源,自旋次数过多会消耗CPU。可通过合理设置锁的公平性(非公平锁比公平锁自旋次数少),减少自旋开销。
AQS支持公平锁和非公平锁两种模式,不同模式的性能差异较大:
-
公平锁:线程按FIFO顺序获取资源,避免线程饥饿,但会增加队列操作的开销(线程唤醒后需再次检查是否为队列头部),性能略低。
-
非公平锁:线程获取资源时,会先尝试CAS获取锁,无需排队,减少队列操作开销,性能更高,但可能导致线程饥饿(个别线程长时间无法获取锁)。
调优建议:高并发场景下,优先使用非公平锁,牺牲少量公平性换取更高的并发性能;若业务要求必须公平(如避免线程饥饿),则使用公平锁。
六、总结
AQS是Java并发编程的核心同步骨架,以状态变量state和CLH同步队列为核心,通过模板方法模式封装通用同步逻辑,支持独占和共享两种模式,为JUC同步组件提供底层支撑。掌握其原理与源码,既能理解现有同步组件的工作机制,也能自定义符合业务需求的同步工具,结合性能调优技巧,可在高并发场景下提升程序效率与安全性。