AQS-模板方法

AQS 的模板方法是一系列 public final 方法,它们定义了线程获取与释放同步状态的固定算法骨架。子类无法重写它们,只能通过实现钩子方法来嵌入自己的同步逻辑。

以下按照独占模式 、共享模式 和可中断/超时变体三个维度,对核心模板方法进行源码级详解。


一、独占模式模板方法

1. acquire(int arg)

作用 :独占式获取资源,不响应中断,失败则进入队列等待直到成功。

java 复制代码
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

内部调用链:

  1. tryAcquire(arg):钩子方法,快速尝试获取锁。
  2. addWaiter(Node.EXCLUSIVE) :获取失败时,将当前线程包装成独占模式 节点,原子地加入 CLH 队列尾部。
  3. acquireQueued(node, arg) :进入自旋 + 阻塞 循环,直到获取锁成功。
    • 若前驱是 head,则再次尝试 tryAcquire。
    • 否则,将前驱状态设为 SIGNAL,然后 LockSupport.park(this) 阻塞自己。
    • 被唤醒后继续循环。
  4. selfInterrupt() :如果在等待过程中被中断过,acquireQueued 返回 true,此时补偿一次中断标记。

关键点 :该方法忽略中断,即线程在等待中被中断时不会抛出异常,只是记录中断状态。


2. acquireInterruptibly(int arg)

作用 :独占式获取资源,响应中断 。若在等待中被中断,则抛出 InterruptedException。

java 复制代码
public final void acquireInterruptibly(int arg) throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (!tryAcquire(arg))
        doAcquireInterruptibly(arg);
}

doAcquireInterruptibly 核心逻辑:

java 复制代码
private void doAcquireInterruptibly(int arg) throws InterruptedException {
    final Node node = addWaiter(Node.EXCLUSIVE);
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null;
                return;
            }
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                // 关键差异:检测到中断时直接抛出异常
                throw new InterruptedException();
        }
    } catch (Throwable t) {
        cancelAcquire(node);
        throw t;
    }
}

与 acquire 的区别 :在 parkAndCheckInterrupt() 返回 true(表示被中断唤醒)时,直接抛出异常,而非仅记录中断状态。


3. tryAcquireNanos(int arg, long nanosTimeout)

作用 :独占式获取资源,响应中断且支持超时 。超时未获取到则返回 false。

核心逻辑:

  • 计算剩余超时时间 deadline = System.nanoTime() + nanosTimeout。
  • 在循环中:
    • 检查中断。
    • 尝试获取锁。
    • 若剩余时间 ≤ spinForTimeoutThreshold(1000 纳秒),则不再阻塞,改为自旋等待,避免阻塞/唤醒的开销大于超时时间。
    • 否则 LockSupport.parkNanos(this, nanosTimeout) 阻塞指定时间。
  • 超时后返回 false,并取消节点。

4. release(int arg)

作用:独占式释放资源。

java 复制代码
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

流程:

  1. 调用钩子 tryRelease。
  2. 若返回 true(资源完全释放),则获取当前 head 节点。
  3. 若 head 存在且状态不为 0(通常为 SIGNAL),则调用 unparkSuccessor(h) 唤醒后继节点。

二、共享模式模板方法

1. acquireShared(int arg)

作用 :共享式获取资源,不响应中断。

java 复制代码
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

doAcquireShared 核心逻辑:

java 复制代码
private void doAcquireShared(int arg) {
    final Node node = addWaiter(Node.SHARED);
    boolean interrupted = false;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    // 获取成功,设置头节点并向后传播唤醒
                    setHeadAndPropagate(node, r);
                    p.next = null;
                    if (interrupted) selfInterrupt();
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                interrupted = true;
        }
    } catch (Throwable t) {
        cancelAcquire(node);
        throw t;
    }
}

关键差异 setHeadAndPropagate:

  • 获取成功后,不仅将自己设为 head,还会检查 tryAcquireShared 的返回值 r。
  • 若 r > 0(表示还有剩余资源),则调用 doReleaseShared() 继续唤醒下一个共享节点 ,实现链式唤醒传播 。这是 CountDownLatch 和 Semaphore 能一次性唤醒一批线程的原因。

2. acquireSharedInterruptibly(int arg)

作用 :共享式获取,响应中断 。逻辑与 acquireShared 类似,但在检测到中断时抛出 InterruptedException。


3. tryAcquireSharedNanos(int arg, long nanosTimeout)

作用 :共享式获取,响应中断且支持超时。


4. releaseShared(int arg)

作用:共享式释放资源。

java 复制代码
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

doReleaseShared 核心逻辑:

  • 自旋 + CAS 修改 head.waitStatus。
  • 若 head 状态为 SIGNAL,则 CAS 将其改为 0,并唤醒后继节点。
  • 若 head 状态为 0,则 CAS 将其改为 PROPAGATE(传播状态),确保唤醒动作能传播到后续节点。

三、条件队列相关模板方法

AQS 内部还有一个 ConditionObject 类,实现了 Condition 接口,用于精细化线程调度。其模板方法包括:

方法 作用
await() 释放锁,进入条件队列阻塞,等待 signal
signal() 将条件队列中的第一个节点转移到同步队列,使其有机会重新获取锁
signalAll() 将所有条件队列节点转移到同步队列

这些方法也遵循模板方法模式,内部调用 isHeldExclusively() 钩子检查独占状态,并操作条件队列(单向链表)。


四、模板方法执行全链路图解

以独占锁 acquire 成功和失败两条路径为例,展示模板方法如何串联钩子方法与底层队列:

graph TD subgraph 成功路径 A[调用 acquire] --> B{tryAcquire?} B -- true --> C[直接返回,持有锁] end subgraph 失败路径 B -- false --> D[addWaiter 创建 EXCLUSIVE 节点入队] D --> E[acquireQueued 自旋循环] E --> F{前驱是 head?} F -- 否 --> G[shouldParkAfterFailedAcquire 设前驱为 SIGNAL] G --> H[parkAndCheckInterrupt 阻塞] H --> E F -- 是 --> I{tryAcquire?} I -- false --> G I -- true --> J[setHead 出队] J --> K[返回中断状态] end

五、模板方法总结对照表

模板方法 模式 响应中断 支持超时 核心内部调用
acquire 独占 ❌ ❌ tryAcquire → addWaiter → acquireQueued
acquireInterruptibly 独占 ✅ ❌ tryAcquire → doAcquireInterruptibly
tryAcquireNanos 独占 ✅ ✅ tryAcquire → doAcquireNanos
release 独占 --- --- tryRelease → unparkSuccessor
acquireShared 共享 ❌ ❌ tryAcquireShared → doAcquireShared → setHeadAndPropagate
acquireSharedInterruptibly 共享 ✅ ❌ 同上,但中断抛异常
tryAcquireSharedNanos 共享 ✅ ✅ 同上,加超时控制
releaseShared 共享 --- --- tryReleaseShared → doReleaseShared

这些模板方法构成了 AQS 的并发调度引擎 ,它们确保所有基于 AQS 的同步组件在排队策略、阻塞唤醒、中断处理、超时控制 上行为一致且性能最优。子类只需专注于资源获取与释放的业务判定即可。

相关推荐
wangchunyu1147 小时前
JAVA CompletableFuture异步编程实战
java·开发语言·python
paopaokaka_luck8 小时前
流浪动物救助与收养系统(DeepSeek AI对话、 ECharts多维分析、救助流程衔接、领养与回访衔接、 捐赠确认与公示、志愿者协作)
java·前端·javascript·人工智能·mysql·echarts·推荐算法
Thomas.Sir8 小时前
第9课:SpringBoot4.x Web开发基础:WebMvc自动配置、静态资源、请求参数接收
java·spring boot
程序 代码狂人8 小时前
切片表与分区表的区别(数据开发面试版)
java·大数据·开发语言·数据仓库·算法
2501_944521599 小时前
Deepmerge 跨端三方库仓颉适配全流程
java·python·eclipse·github
南归北隐10 小时前
Spring AI Alibaba Graph框架实现MCP数据库查询实战
java·后端·spring ai·mcp·spring ai mcp
古法老登瞎折腾10 小时前
大数据智能运维实战(九):让 AI 查日志——找到异常,也保留上下文
java·大数据·运维·人工智能·spring ai·mcp
ai大模型-探索者11 小时前
Function Call与MCP(区别、联系与实战)
java·服务器·网络
略略略咯咯11 小时前
啊啊啊啊啊
java·服务器·数据库
SL_staff11 小时前
运维救火实战:钉钉API变更致薪资计算中断,如何用可视化逻辑引擎5分钟热修复
java·开源·全栈