目录
正在加载目录…
专栏文章
专栏文章
Java 专栏
1. Java:跨平台语言与生态全景 2. Java 并发:JMM、锁与线程池原理 3. JVM:类加载、内存、GC 与性能诊断 4. Java 性能调优:CPU、内存、锁与 IO 排查 5. Java 新特性速查:从 Lambda 到虚拟线程 6. Java 业务开发:高频陷阱与 Code Review 清单 7. Java 核心知识:集合、并发与 JVM 面试要点 8. AQS 原理:同步队列与加锁解锁全流程 9. JVM 与 Linux 内存:分区、分配与 GC 边界 10. AtomicLong 与 LongAdder:并发计数器选型 11. Java 引用与内存泄漏:六类场景与修复 12. Java 测试实践:JUnit、Mockito 与 Testcontainers 13. Java 并发入门:线程、锁与线程池全景

AQS 原理:同步队列与加锁解锁全流程

发布于 2026-06-17 14:40 · 最后编辑于 2026-07-31 15:51 · 字数 4,804 👁 328 次阅读

AQS(AbstractQueuedSynchronizer)是 Java 并发包的核心骨架,ReentrantLock、Semaphore、CountDownLatch 等都建立在它之上。本文以 JDK 8 源码为基础,从数据结构到加锁/解锁全流程逐层拆解。

目录

章节说明
AQS 是什么定位与设计思想
核心数据结构Node、state、CLH 队列
模板方法:子类只需实现 5 个方法tryAcquire / tryRelease 等
加锁全流程acquire → addWaiter → acquireQueued
解锁全流程release → unparkSuccessor
CANCELLED 节点的产生与清理cancelAcquire 详解
中断处理机制协作式中断的设计
公平锁 vs 非公平锁的差异hasQueuedPredecessors
可重入性实现state 累加与还原
AQS 在 JUC 中的应用各工具类与 state 的对应关系
自定义同步器示例手写一个简单互斥锁

AQS 是什么

AQS 提供三项核心能力:

  1. 原子管理同步状态:用一个 volatile int state 表示所有同步状态
  2. 阻塞/唤醒线程:依赖 LockSupport.park / unpark,不使用低效的 Object.wait
  3. FIFO 等待队列:CLH 变体的双向链表,管理竞争失败的线程

设计核心:子类只负责定义"state 的含义"和"如何修改 state",其余排队、阻塞、唤醒逻辑全由 AQS 框架完成。

AQS 分五层:

第 1 层(API 层):Lock.lock() / unlock() 等对外接口
第 2 层(锁获取):acquire() / release() —— AQS 框架实现
第 3 层(等待队列):addWaiter() / acquireQueued() —— AQS 框架实现
第 4 层(阻塞/唤醒):LockSupport.park / unpark
第 5 层(基础数据):volatile state、Node 双向链表、CAS 原语

核心数据结构

../../assets/06 工程算法/aqs structure

state 字段

// java.util.concurrent.locks.AbstractQueuedSynchronizer
private volatile int state;

protected final int getState()                              // volatile 读
protected final void setState(int newState)                 // volatile 写
protected final boolean compareAndSetState(int e, int u)    // CAS 更新

state 的具体含义由子类定义:

实现类state 含义
ReentrantLock0 = 未锁;≥1 = 被持有次数(可重入计数)
Semaphore当前剩余许可数
CountDownLatch剩余计数,归零后所有 await 解除
ReentrantReadWriteLock高 16 位 = 读锁计数,低 16 位 = 写锁计数

Node 节点

CLH 等待队列中每个节点对应一个等待线程:

// java.util.concurrent.locks.AbstractQueuedSynchronizer.Node
static final class Node {
    // 线程等待锁的模式
    static final Node SHARED    = new Node();   // 共享模式(如读锁、Semaphore)
    static final Node EXCLUSIVE = null;         // 独占模式(如写锁、ReentrantLock)

    // waitStatus 枚举值
    static final int CANCELLED =  1;  // 线程放弃等待(超时或中断)
    static final int SIGNAL    = -1;  // 后继节点等待被唤醒
    static final int CONDITION = -2;  // 节点在条件队列中等待
    static final int PROPAGATE = -3;  // 共享模式下需要向后传播唤醒

    volatile int   waitStatus;  // 节点状态,默认 0
    volatile Node  prev;        // 前驱节点
    volatile Node  next;        // 后继节点
    volatile Thread thread;     // 该节点绑定的线程
    Node nextWaiter;            // 条件队列中的下一个节点(Condition Queue 专用)
}
waitStatus含义
初始化0节点刚入队,尚未设置状态
SIGNAL-1前驱持有锁,我(后继)已挂起,释放时请唤醒我
CANCELLED1放弃等待,需要从队列中清除
CONDITION-2在条件队列中等待(ConditionObject 专用)
PROPAGATE-3共享模式下唤醒需要向后传播

为什么 waitStatus 记在前驱节点上,而不是自己身上? 因为"谁负责唤醒我,谁就记着这件事"——前驱释放锁时会检查自己的 waitStatus,看是否需要唤醒后继。如果记在自己节点上,前驱释放锁时还要去读后继的状态,多一次跨节点访问,且在后继节点还未完全初始化时可能有并发问题。

日常加锁/解锁流程里最核心的只有两个状态:0(刚入队)和 SIGNAL(已挂起,请唤醒我)。线程挂起前必须先把前驱的 waitStatus 设为 SIGNAL,这是"我要睡了,前驱你记得叫醒我"的协议。

CLH 变体双向队列结构

为什么不用普通队列

如果用普通队列实现锁等待,所有等待线程会盯着同一个变量(锁的 state)自旋:

线程1 ──┐
线程2 ──┤──▶ 轮询 state == 0?
线程3 ──┘

问题:锁释放时 state 被写入,所有等待线程的 Cache Line 全部失效,N 个线程同时涌来读新值——这叫 Cache Line 惊群(Thundering Herd),线程越多性能越差。

CLH 的解法:每个线程只盯自己的前驱

CLH 是一个虚拟链表,每个节点有 locked 标志,线程只监视自己前驱节点的状态:

[node1: locked=false] ← [node2: 盯 node1.locked] ← [node3: 盯 node2.locked]
     ↑ 当前持锁             ↑ 等待中                    ↑ 等待中

锁释放时只把自己节点locked = false

  • 只有直接后继(node2)的 Cache Line 失效
  • node3 完全不受影响,继续在本地 Cache 自旋
  • N 个线程等待,释放锁只影响 1 个线程,彻底消除惊群
普通队列CLH 队列
自旋目标所有线程盯同一变量每个线程盯自己前驱
释放锁时 Cache 失效范围全部等待线程仅直接后继
NUMA 友好?❌ 跨核竞争✅ 本地 Cache 自旋
公平性不保证天然 FIFO

AQS 的变体:双向链表 + park 挂起

原始 CLH 是纯自旋,Java 线程纯自旋浪费 CPU,所以 AQS 做了两处改造:

  1. 单向 → 双向:加了 prev 指针,方便跳过 CANCELLED 节点
  2. 纯自旋 → 自旋几次后 park 挂起:减少 CPU 空转
// AQS acquireQueued 简化逻辑
for (;;) {
    if (前驱是 head && tryAcquire()) break;  // 短暂自旋尝试
    if (shouldPark) {
        LockSupport.park(this);              // 自旋失败,真正挂起
    }
}

队列结构:

graph LR
    H["Head(虚节点)<br/>thread=null<br/>waitStatus=-1"] --> A["Node-A<br/>Thread-2<br/>waitStatus=-1"]
    A --> B["Node-B<br/>Thread-3<br/>waitStatus=-1"]
    B --> T["Tail(Node-C)<br/>Thread-4<br/>waitStatus=0"]
    T --> B
    B --> A
    A --> H
    style H fill:#eee,stroke:#999

关键设计:头节点是虚节点(dummy node),不绑定任何线程,仅作占位。真正第一个等待线程在 head.next

虚节点的真正作用:统一边界条件

虚节点不是为了解决并发安全问题(CAS 入队本身已保证),而是为了让第一个入队的真实线程也能满足 p == head 这个条件

acquireQueued 的核心逻辑是:

if (p == head && tryAcquire(arg)) {
    setHead(node);  // 获锁成功,把自己升为新 head
    ...
}

只有前驱是 head 的线程,才有资格去 tryAcquire 如果没有虚节点,第一个入队的线程自己就是 head,前驱为 null,p == head 永远不成立,第一个等待者会被饿死。有了虚节点,所有等待线程的逻辑完全一致,不需要对"我是不是队列里第一个"做特判。

Head 是动态游标,不是固定虚节点

Head 不是一个永远不变的占位节点,而是随着锁的获取不断向后推进:

初始:  Head(虚节点)← → Node-A(Thread-2)← → Node-B(Thread-3)

Thread-2 获锁后:
        setHead(Node-A)         // Node-A 升为新 head
        Node-A.thread = null    // 清空 thread,变成新虚节点
        原 Head 断开,被 GC 回收

变成:  Head(原 Node-A, thread=null)← → Node-B(Thread-3)

获锁成功的节点会把自己"变成"虚节点——清空 thread 引用,成为下一轮的哨兵。Head 始终代表"当前持锁线程的占位",是一个不断前进的游标,而不是静态的哨兵。

模板方法:子类只需实现 5 个方法

// 子类需要覆盖的 5 个钩子方法(默认抛 UnsupportedOperationException)
protected boolean tryAcquire(int arg)        // 独占:尝试获取锁
protected boolean tryRelease(int arg)        // 独占:尝试释放锁
protected int     tryAcquireShared(int arg)  // 共享:负数=失败,0=成功无余量,正数=成功有余量
protected boolean tryReleaseShared(int arg)  // 共享:释放后是否需要唤醒后继
protected boolean isHeldExclusively()        // 当前线程是否独占持有(Condition 依赖)

ReentrantLock 是独占锁,只实现 tryAcquire + tryRelease,其余全由框架承包。

加锁全流程

../../assets/08 AQS 原理深度解析/file 20260605123647648

非公平锁 ReentrantLock.lock() 为例,串联整个加锁链路。

第一步:lock() 入口

// java.util.concurrent.locks.ReentrantLock.NonfairSync
final void lock() {
    // 快速路径:state=0 时直接 CAS 抢锁,不入队
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);  // 快速路径失败,走 AQS 标准流程
}

非公平锁在入队前先"插队"抢一次,抢到直接返回,抢不到才走 acquire

第二步:acquire()

// java.util.concurrent.locks.AbstractQueuedSynchronizer
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&                         // 再次尝试获取锁
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 失败则入队并阻塞
        selfInterrupt();  // 补偿中断标志
}

acquire 是一行逻辑密集的代码,拆解为三步:

  1. tryAcquire(arg):再尝试一次(非公平锁会再插队)
  2. addWaiter(Node.EXCLUSIVE):失败则将当前线程包装为 Node 加入队列尾部
  3. acquireQueued(...):在队列中自旋等待,直到获取到锁,返回是否被中断过

第三步:addWaiter() — 入队

// java.util.concurrent.locks.AbstractQueuedSynchronizer
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        // 快速路径:CAS 将 node 设为新尾节点
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 队列为空或 CAS 失败 → 进入自旋初始化/入队
    enq(node);
    return node;
}

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) {
            // 队列未初始化:创建虚拟头节点
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

注意:头节点的初始化用 new Node()(无参构造),是一个不绑定线程的虚节点。

入队过程可视化(线程 2、3 相继入队):

sequenceDiagram
    participant T1 as Thread-1(持锁)
    participant T2 as Thread-2
    participant T3 as Thread-3
    participant Q  as CLH Queue

    T2->>Q: addWaiter → enq → 初始化虚头节点 + 自身入队尾
    Note over Q: Head(虚) ←→ Node(T2)
    T3->>Q: addWaiter → CAS 入队尾
    Note over Q: Head(虚) ←→ Node(T2) ←→ Node(T3)

第四步:acquireQueued() — 自旋等待

// java.util.concurrent.locks.AbstractQueuedSynchronizer
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();  // 获取前驱节点
            // 只有前驱是 head(即我是第一个等待者),才有资格尝试获取锁
            if (p == head && tryAcquire(arg)) {
                setHead(node);   // 获取成功:自己成为新的虚头节点
                p.next = null;   // 原头节点断链,help GC
                failed = false;
                return interrupted;
            }
            // 获取失败:判断是否需要挂起
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);  // 异常退出时取消节点
    }
}

setHead 把获取到锁的节点变为新虚头节点(清空 thread 和 prev),原头节点被 GC:

private void setHead(Node node) {
    head = node;
    node.thread = null;  // 虚节点不持有线程引用
    node.prev = null;
}

第五步:shouldParkAfterFailedAcquire() — 决定是否挂起

// java.util.concurrent.locks.AbstractQueuedSynchronizer
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        // 前驱已设置 SIGNAL,我可以安心挂起,等前驱唤醒我
        return true;
    if (ws > 0) {
        // 前驱是 CANCELLED(>0),跳过所有取消节点,找到有效前驱
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        // 前驱状态为 0 或 PROPAGATE:CAS 将其设为 SIGNAL
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;  // 本次不挂起,下一轮循环再检查
}

逻辑:只有确认前驱的 waitStatus == SIGNAL,当前线程才真正挂起。否则先把前驱设为 SIGNAL,下一次循环进来就会返回 true 并挂起。

flowchart TD
    A["shouldParkAfterFailedAcquire<br/>前驱 waitStatus = ?"] --> B{ws == SIGNAL?}
    B -->|是| C["return true<br/>(可以挂起)"]
    B -->|ws > 0 取消| D["跳过取消节点<br/>重新链接前驱"]
    D --> E["return false<br/>(重新循环)"]
    B -->|ws == 0 或 PROPAGATE| F["CAS 设前驱为 SIGNAL"]
    F --> E
    style C fill:#cfc,stroke:#060

第六步:parkAndCheckInterrupt() — 真正挂起

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);       // 挂起当前线程,直到被 unpark 或中断
    return Thread.interrupted();  // 返回并清除中断标志
}

LockSupport.park 是无锁的线程挂起,比 Object.wait 更轻量,且不需要持有监视器锁。

为什么不复用 suspend/resume 或 wait/notify

JDK 1.5 之前有两套线程挂起机制,但都有根本性缺陷:

Thread.suspend() / resume()(已废弃):

  • 致命问题:suspend 挂起时不释放锁。如果线程持有锁时被 suspend,其他所有等待该锁的线程永久死锁
  • 通知丢失resume() 先于 suspend() 执行,线程永久挂起

Object.wait() / notify()

  • 必须持有 synchronized 锁才能调用,与锁强耦合,无法单独使用
  • 同样有通知丢失问题,需要额外的标志位防范
  • 设计粒度错误wait/notify 是"对象级别"的概念,假设挂起和唤醒永远发生在同一把锁的上下文里;而 AQS 需要的是"挂起/唤醒某个特定线程",两者抽象层次不同,修补解决不了根本问题

LockSupport.park / unpark 的解法

底层每个线程持有一个许可证(permit,值只有 0 和 1)

  • unpark(t):给线程 t 发一张许可证(最多积累 1 张)
  • park():消费一张许可证;没有则挂起

关键:unpark 可以在 park 之前调用,许可证会被保留,下次 park 直接消费不阻塞——彻底解决通知丢失问题,且完全不依赖任何锁。

suspend/resumewait/notifypark/unpark
需要持锁?✅ 必须
挂起时释放锁?不释放✅ 释放— (无锁概念)
通知可以提前?❌ 丢失❌ 丢失✅ 许可证保留
与锁耦合?✅ 强耦合❌ 完全解耦

解锁全流程

unlock() → release() → tryRelease() → unparkSuccessor()

// ReentrantLock
public void unlock() {
    sync.release(1);
}

// AQS
public final boolean release(int arg) {
    if (tryRelease(arg)) {           // 子类释放 state
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);      // 唤醒队列中下一个等待线程
        return true;
    }
    return false;
}

// ReentrantLock.Sync(公平/非公平共用)
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);  // 清空持有线程
    }
    setState(c);   // 写 volatile,建立 Happens-Before
    return free;   // 只有完全释放(c==0)才返回 true
}

releaseh.waitStatus != 0 的三种情况:

条件含义动作
h == null队列未初始化,无竞争不唤醒
h != null && waitStatus == 0后继线程还在运行,尚未挂起不唤醒
h != null && waitStatus < 0后继线程已挂起,等待唤醒唤醒

unparkSuccessor() — 找到并唤醒后继

private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);  // 清除头节点状态

    Node s = node.next;
    // 如果 next 为 null 或已取消,从尾部往前找第一个有效节点
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;  // 一直找到最靠近 head 的有效节点
    }
    if (s != null)
        LockSupport.unpark(s.thread);  // 唤醒
}

为什么从尾部往前找,而不是从前往后?

入队操作不是原子的,addWaiter 中有这样的顺序:

node.prev = pred;          // ① prev 先建立
compareAndSetTail(...);    // ② tail 指针原子更新
pred.next = node;          // ③ next 最后建立(可能还没完成)

若在 ② 和 ③ 之间执行 unparkSuccessor,从前往后遍历会因 pred.next 尚未赋值而漏掉新节点。而 prev 在 ① 已经可靠建立,从尾部往前遍历则不会遗漏。

CANCELLED 节点的产生与清理

节点在以下情况进入 CANCELLED 状态:acquireQueuedfinally 块中,若 failed == true(通常由超时或中断引发异常),调用 cancelAcquire

private void cancelAcquire(Node node) {
    if (node == null) return;
    node.thread = null;  // 解除线程引用

    // 跳过前面所有已取消的节点
    Node pred = node.prev;
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;

    Node predNext = pred.next;
    node.waitStatus = Node.CANCELLED;  // 标记自己为取消

    // 分三种情况处理
    if (node == tail && compareAndSetTail(node, pred)) {
        // 情况 1:我是尾节点 → 直接把前驱设为新尾节点
        compareAndSetNext(pred, predNext, null);
    } else {
        int ws;
        if (pred != head
            && ((ws = pred.waitStatus) == Node.SIGNAL
                || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL)))
            && pred.thread != null) {
            // 情况 2:中间节点 → 让前驱的 next 跳过我,指向我的后继
            Node next = node.next;
            if (next != null && next.waitStatus <= 0)
                compareAndSetNext(pred, predNext, next);
        } else {
            // 情况 3:我是 head 的直接后继 → 唤醒我的后继,让它重新竞争
            unparkSuccessor(node);
        }
        node.next = node;  // help GC(自引用)
    }
}

三种情况图示:

graph LR
    subgraph "情况1:取消节点是尾节点"
        H1["Head"] --> A1["Node-A"] --> X1["Node-X<br/>CANCELLED"]
        H1 -. "新 tail" .-> A1
    end
    subgraph "情况2:取消节点在中间"
        H2["Head"] --> A2["Node-A"] --> X2["Node-X<br/>CANCELLED"] --> B2["Node-B"]
        A2 -. "跳过 X" .-> B2
    end
    subgraph "情况3:取消节点是 head 直接后继"
        H3["Head"] --> X3["Node-X<br/>CANCELLED"] --> B3["Node-B"]
        X3 -. "唤醒 B" .-> B3
    end

为什么只改 next,不改 prev? cancelAcquire 执行时,当前节点的前驱可能正在 shouldParkAfterFailedAcquire 里操作,修改 prev 不安全。prev 的清理由 shouldParkAfterFailedAcquire 中的跳过取消节点逻辑负责(那段代码执行时锁已被占有,前驱不会再变化,安全)。

中断处理机制

AQS 对中断采用协作式处理:不立即响应,而是记录下来,获取锁后补偿

// acquireQueued 内部
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
    interrupted = true;  // 记录"曾经被中断过"

// 最终拿到锁后返回 interrupted
return interrupted;

// acquire 收到 true,调用 selfInterrupt 补偿
static void selfInterrupt() {
    Thread.currentThread().interrupt();  // 重新设置中断标志
}

完整流程:

  1. 线程在 park 中被中断唤醒
  2. Thread.interrupted() 返回 true 并清除中断标志
  3. interrupted = true 记录
  4. 继续自旋,直到获取到锁
  5. acquire 收到 interrupted=true,调用 selfInterrupt() 重新设置中断位
  6. 调用方可以通过检查 Thread.currentThread().isInterrupted() 得知曾被中断

公平锁 vs 非公平锁的差异

两者 tryAcquire 的唯一区别是是否检查队列中有前驱

// 非公平锁(NonfairSync)
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 直接 CAS 抢,不检查队列
        if (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;
}

// 公平锁(FairSync)
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 多了这个判断:队列为空或我是第一个,才能抢
        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:返回 true 表示有其他线程在队列中排在前面
public final boolean hasQueuedPredecessors() {
    Node t = tail;
    Node h = head;
    Node s;
    // h != t 说明队列有节点;h.next == null 说明正在初始化;
    // h.next.thread != current 说明排在我前面的是别人
    return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
}
维度非公平锁公平锁
吞吐量高(减少上下文切换)
等待时间不均匀,可能饥饿均匀,FIFO 顺序
适用场景高并发、锁持有时间短任务等待时间敏感

可重入性实现

ReentrantLock 的可重入靠 state 累加实现:

// 加锁:同一线程每重入一次,state +1
if (c == 0) {
    // 首次获取
    if (compareAndSetState(0, acquires)) {
        setExclusiveOwnerThread(current);
        return true;
    }
} else if (current == getExclusiveOwnerThread()) {
    // 同一线程重入:state 累加
    int nextc = c + acquires;
    if (nextc < 0) throw new Error("Maximum lock count exceeded");
    setState(nextc);
    return true;
}

// 解锁:每次 -1,直到 0 才真正释放
int c = getState() - releases;
if (c == 0) {
    free = true;
    setExclusiveOwnerThread(null);
}
setState(c);
return free;  // c==0 时返回 true,触发唤醒后继

AQS 在 JUC 中的应用

同步工具state 含义模式
ReentrantLock重入次数(0=无锁)独占
ReentrantReadWriteLock高 16 位=读锁次数,低 16 位=写锁次数独占 + 共享
Semaphore剩余许可数共享
CountDownLatch剩余计数(归零后所有等待解除)共享
ThreadPoolExecutor.Worker0=空闲,1=运行(防止中断运行中 Worker)独占

自定义同步器示例

只需继承 AQS,实现 tryAcquiretryRelease,就能得到一个完整的互斥锁:

public class SimpleMutex {

    private static class Sync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire(int arg) {
            // state: 0=未锁,1=已锁
            return compareAndSetState(0, 1);
        }

        @Override
        protected boolean tryRelease(int arg) {
            setState(0);
            return true;
        }

        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }
    }

    private final Sync sync = new Sync();

    public void lock()   { sync.acquire(1); }
    public void unlock() { sync.release(1); }
}

使用效果(两线程各加 10000 次,结果精确为 20000):

SimpleMutex mutex = new SimpleMutex();
int[] count = {0};

Runnable task = () -> {
    mutex.lock();
    try {
        for (int i = 0; i < 10000; i++) count[0]++;
    } finally {
        mutex.unlock();
    }
};

Thread t1 = new Thread(task), t2 = new Thread(task);
t1.start(); t2.start();
t1.join();  t2.join();
System.out.println(count[0]);  // 始终输出 20000

参考资料

  • Java 8 AQS 官方文档
  • Lea D. The java.util.concurrent synchronizer framework. Science of Computer Programming, 2005
  • 《Java 并发编程实战》(Brian Goetz 等著)
← 返回列表

评论 (0)

暂无评论,来留下第一条吧。
登录注册 后才能发表评论