Java并发:我用修仙小说讲AQS

25 阅读4分钟

一、引子:修仙界的资源争夺

话说在Java修仙大陆,有一神秘宗门名为并发宗。宗内有一至宝——灵脉核心,此物乃修炼圣地,得者可修为大进。

然灵脉唯一,弟子万千。如何公平分配?宗主Doug Lea老祖遂创AQS大阵(AbstractQueuedSynchronizer),此乃镇宗之宝。

现实映射

  • 灵脉核心 = 临界区资源(如数据库连接、共享变量)
  • 弟子 = 线程
  • AQS大阵 = ReentrantLockSemaphoreCountDownLatch的底层实现

二、宗门大阵(AQS)的核心设计

2.1 阵眼:state变量

// AQS核心:一个volatile int,记录灵脉状态
private volatile int state;
修仙术语技术含义
state = 0灵脉空闲,无人占用
state = 1被独占(单弟子修炼)
state = N被共享(N个弟子同时修炼)
volatile阵眼发光,全宗门可见(内存可见性)

2.2 阵图:FIFO双端队列

┌─────────────────────────────────────┐
│           AQS宗门大阵               │
│                                     │
│   head → [弟子A] ←→ [弟子B] ←→ [弟子C] ← tail
│          (正在修炼)   (排队等待)   (排队等待)
│                                     │
│  state = 1(灵脉被A独占)            │
└─────────────────────────────────────┘
// AQS内部队列节点
static final class Node {
    volatile int waitStatus;    // 弟子状态:修炼中/等待/取消
    volatile Node prev;         // 前一位师兄
    volatile Node next;         // 后一位师弟
    volatile Thread thread;     // 弟子真身(线程)
}

三、独占锁:单传弟子的传承仪式

3.1 场景:ReentrantLock(可重入独占锁)

大长老宣布:灵脉核心一次只容一人修炼,但允许同一人多次进入(可重入)。

3.2 修仙流程图

弟子A申请灵脉
    │
    ▼
┌─────────────┐
│  tryAcquire() │ ← 阵眼state是否为0?
│   尝试抢占    │
└─────────────┘
    │
    ├── state=0 ──→ CAS修改state=1 ──→ 获得灵脉,开始修炼
    │                                              │
    │                                              ▼
    │                                       修炼完毕release()
    │                                       state=0,唤醒队列下一个
    │
    └── state=1 ──→ 是同一人? ──→ state+1(重入次数+1)
              │
              └── 不同人? ──→ 加入FIFO队列, park()休眠等待

3.3 核心源码(修仙注释版)

// 弟子尝试抢占灵脉(AQS.acquire简化版)
public final void acquire(int arg) {
    // 第一步:tryAcquire,看运气能不能直接抢到
    if (!tryAcquire(arg) &&
        // 第二步:抢不到,加入宗门排队队列
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
        // 第三步:排队期间被中断,补中断标记
        selfInterrupt();
    }
}

// 加入排队队列(像挂号)
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    if (pred != null) {        // 队列已有师兄
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {  // CAS挂到队尾
            pred.next = node;
            return node;
        }
    }
    enq(node);  // 队列空或CAS失败,自旋入队
    return node;
}

// 排队等待(宗门规矩:按顺序来)
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {  // 自旋,心魔誓约:不放弃
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {  // 轮到我了!
                setHead(node);  // 成为阵首,开始修炼
                p.next = null;
                failed = false;
                return interrupted;
            }
            // 还没轮到我,休眠等待被唤醒
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);  // 走火入魔,取消资格
    }
}

四、共享锁:宗门宝库的开放日

4.1 场景:Semaphore(信号量)

宗主宣布:宝库中有10件灵器,可同时供10位弟子取用,先到先得。

4.2 核心区别

独占锁(ReentrantLock)共享锁(Semaphore)
state含义0或1(有人/无人)N(剩余名额)
获取成功条件state从0→1state从N→N-1(N>0)
唤醒策略只唤醒下一个可能唤醒多个

4.3 源码差异

// 共享锁获取:只要还有名额,就允许进入
protected int tryAcquireShared(int acquires) {
    for (;;) {
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;  // >=0成功,<0失败
    }
}

// 释放时:可能唤醒多个等待弟子
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();  // 唤醒后继,传播唤醒
        return true;
    }
    return false;
}

五、Condition:心魔誓约与传音入密

5.1 场景

弟子A修炼到关键瓶颈,需要特定条件(如"灵气浓度>80")才能突破。此时他:

  1. 暂时释放灵脉(让出位置)
  2. 进入静室等待(Condition队列)
  3. 条件满足后被唤醒(重新排队抢灵脉)

5.2 结构图

┌─────────────────────────────────────────┐
│              AQS主队列                  │
│  head → [弟子A][弟子B][弟子C]     │
│          (修炼中)                        │
└─────────────────────────────────────────┘
            │
            ▼ 调用 condition.await()
┌─────────────────────────────────────────┐
│           Condition静室队列              │
│  [弟子A][弟子D]  (等待特定条件)       │
│  (释放灵脉,在此休眠)                     │
└─────────────────────────────────────────┘
            │
            ▼ 其他弟子调用 condition.signal()
            唤醒弟子A,重新进入主队列排队

5.3 代码示例

ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();

// 弟子A的修炼流程
lock.lock();
try {
    while (灵气浓度 < 80) {  // 防止虚假唤醒
        condition.await();    // 释放锁,进静室等待
    }
    // 条件满足,继续修炼
    突破瓶颈();
} finally {
    lock.unlock();
}

// 弟子B(管理灵气):
lock.lock();
try {
    增加灵气浓度();
    condition.signalAll();    // 唤醒所有等待的弟子
} finally {
    lock.unlock();
}

六、源码揭秘:从修仙术语到Java代码

修仙术语Java术语源码位置
宗门大阵AQSjava.util.concurrent.locks.AbstractQueuedSynchronizer
阵眼state同步状态private volatile int state
弟子排队CLH队列Node head, tail
单传弟子独占模式Node.EXCLUSIVE
多人同修共享模式Node.SHARED
心魔誓约ConditionConditionObject
传音入密LockSupportLockSupport.park()/unpark()

七、总结:AQS的修仙哲学

  1. state为阵眼:一切同步状态,皆系于此
  2. FIFO为天道:先来后到,公平有序
  3. CAS为法术:原子操作,无锁竞争
  4. LockSupport为休眠:不浪费灵气(CPU),等待唤醒
  5. 可重入为传承:同一线程多次进入,计数管理

AQS之所以强大,在于它将复杂的线程同步抽象为简单的状态管理+队列调度,让万千弟子(线程)井然有序地争夺灵脉(资源)。