Skip to content

AQS 共享模式源码深度解析

一、AQS 核心架构与同步机制

1.1 核心成员变量

java
// 同步状态变量(volatile保证可见性)
private volatile int state;

// CLH队列头尾节点
private transient volatile Node head;
private transient volatile Node tail;

1.2 节点状态定义

java
static final class Node {
    // 等待状态常量及其核心含义:
    // CANCELLED(1):节点取消,可能由于超时或中断
    // SIGNAL(-1):需要唤醒后继节点,当前节点释放锁后必须触发唤醒
    // CONDITION(-2):节点在条件队列中等待,与Condition实现相关
    // PROPAGATE(-3):共享传播状态,用于保证读写可见性及传播唤醒
    static final int CANCELLED = 1;
    static final int SIGNAL = -1;
    static final int CONDITION = -2;
    static final int PROPAGATE = -3;

    // 当前节点的等待状态(通过CAS修改)
    volatile int waitStatus;

    // CLH队列中的前驱和后继节点,形成双向链表结构
    volatile Node prev;  // 前驱节点
    volatile Node next;  // 后继节点

    // 关联的线程对象,表示该节点是由哪个线程创建的
    volatile Thread thread; // 关联线程
}

二、共享模式核心流程解析

2.1 获取共享锁(acquireShared)

java
public final void acquireShared(int arg) {
    // 调用 tryAcquireShared 尝试获取共享锁,返回值表示获取结果:
    // - 如果返回值 >= 0,表示成功获取共享锁,无需进入队列;
    // - 如果返回值 < 0,表示获取失败,需要进入 CLH 队列等待。
    if (tryAcquireShared(arg) < 0) {
        // 进入 CLH 队列,通过自旋和线程挂起的方式等待锁的释放。
        // doAcquireShared 方法会将当前线程封装为共享模式节点,并加入同步队列,
        // 同时在适当条件下尝试重新获取锁或传播唤醒信号。
        doAcquireShared(arg);
    }
}

2.1.1 自旋获取实现(深度剖析)

java
/**
 * 共享模式下获取资源的核心实现:
 * 1. 创建共享节点并入队
 * 2. 自旋检查前驱节点状态
 * 3. 尝试获取资源并处理传播逻辑
 */
private void doAcquireShared(int arg) {
    // 创建共享模式节点并入队(SHARED标记区别于独占模式)
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true; // 获取资源失败标记
    
    try {
        // 自旋获取资源(典型CAS失败重试模式)
        for (;;) {
            final Node p = node.predecessor(); // 获取前驱节点
            
            // 关键路径:当前驱为头节点时尝试获取资源
            if (p == head) {
                int r = tryAcquireShared(arg); // 模板方法,子类实现
                
                // 获取成功后的处理流程
                if (r >= 0) {
                    setHead(node); // 新头节点设置(包含thread清空操作)
                    p.next = null; // 断开原头节点链接帮助GC
                    
                    // 资源剩余量>0时触发传播唤醒(共享模式核心)
                    if (r > 0) {
                        doReleaseShared(); // 唤醒后继共享节点
                    }
                    failed = false;
                    break;
                }
            }
            
            // 线程挂起逻辑(实际包含shouldParkAfterFailedAcquire检查)
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt()) {
                // 响应中断但不抛出异常(AQS标准处理方式)
                break;
            }
        }
    } finally {
        // 异常处理:取消获取并清理节点状态
        if (failed) {
            cancelAcquire(node); // 处理CANCELLED状态
        }
    }
}

2.2 释放共享锁(releaseShared)

java
/**
 * 共享模式下释放同步状态的标准入口
 * 1. 调用 tryReleaseShared 尝试原子释放资源(子类实现)
 * 2. 成功后通过 doReleaseShared 触发传播唤醒机制
 * 
 * @param arg 释放参数,具体语义由子类定义
 * @return 是否成功释放资源
 */
public final boolean releaseShared(int arg) {
    // 模板方法设计模式:
    // 1. 父类定义框架(final防止重写)
    // 2. 子类实现具体逻辑(tryReleaseShared)
    if (tryReleaseShared(arg)) {
        // 唤醒传播机制核心:
        // - 可能唤醒多个共享节点
        // - 确保剩余资源量正确传递
        // - 使用CAS避免并发竞争
        doReleaseShared();  
        return true;
    }
    return false;
}

2.2.1 唤醒传播机制(深度解析版)

java
private void doReleaseShared() {
    for (;;) {
        Node h = head; // 获取当前头节点(volatile读保证可见性)
        
        // 非空队列且存在后继节点需要处理:
        // 1. h != null:确保队列已初始化
        // 2. h != tail:说明有至少一个等待节点
        if (h != null && h != tail) {
            int ws = h.waitStatus; // 获取当前头节点状态(volatile读)
            
            // SIGNAL状态表示后续节点需要唤醒:
            // - 当前节点释放后必须触发唤醒
            // - CAS修改状态避免并发竞争
            if (ws == Node.SIGNAL) {
                // 尝试将等待状态置为0(无信号):
                // compareAndSetWaitStatus是原子操作,失败则重试整个循环
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) {
                    continue; // CAS失败则重试整个循环(典型CAS失败重试模式)
                }
                // 唤醒后继节点(底层调用LockSupport.unpark):
                // 包含异常安全处理,确保线程继续执行
                unparkSuccessor(h); 
            } 
            // PROPAGATE状态确保唤醒传播继续:
            // - 0 → PROPAGATE 确保后续获取能继续传播
            // - 这是共享模式的核心设计之一
            else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) {
                continue; // 状态修改失败时重新迭代(保持自旋一致性)
            }
        }
        
        // 如果头节点未改变,说明传播已完成:
        // 1. h == head:没有新的节点成为头节点
        // 2. break退出循环完成唤醒流程
        // 否则继续循环以处理新的头节点(如新获取成功的节点)
        if (h == head) break;
    }
}

三、典型应用场景分析

3.1 CountDownLatch 实现原理(深度剖析版)

java
/**
 * 共享模式下尝试获取同步状态的核心实现:
 * - 返回值表示获取结果:
 *   - >=0:成功获取,可退出自旋并继续执行后续逻辑
 *   - <0:失败获取,需进入或继续等待
 */
protected int tryAcquireShared(int acquires) {
    // 关键设计点:仅当同步状态(state)为0时允许获取
    // 这体现了CountDownLatch的"门闩"特性:
    // - 计数器未归零前任何线程都无法通过
    // - 归零后所有等待线程被唤醒
    return (getState() == 0) ? 1 : -1;
}

/**
 * 原子化释放同步状态的核心实现:
 * - 使用CAS自旋确保并发安全
 * - 实现了"减到0时触发唤醒传播"的设计语义
 */
protected boolean tryReleaseShared(int releases) {
    for (;;) { // CAS自旋标准写法
        int c = getState(); // volatile读,保证可见性
        
        // 边界处理:若当前状态已是0,直接返回false
        if (c == 0) return false;
        
        // 状态递减操作(非阻塞式原子更新)
        int nextc = c - 1;
        
        // CAS更新state字段:
        // 1. compareAndSetState是Unsafe提供的原子操作
        // 2. 失败则继续自旋重试
        if (compareAndSetState(c, nextc)) {
            // 仅当最终减到0时返回true以触发唤醒传播:
            // - AQS框架约定:返回true将调用doReleaseShared
            // - 这是共享模式唤醒机制的关键触发点
            return nextc == 0;
        }
    }
}

3.2 Semaphore 共享资源控制(深度剖析实现)

java
/**
 * 共享模式下尝试获取信号量资源的核心方法:
 * - 返回值表示获取结果:
 *   - >=0:成功获取资源,remaining为剩余资源数
 *   - <0:资源不足,需进入CLH队列等待唤醒
 */
protected int tryAcquireShared(int acquires) {
    for (;;) { // CAS自旋标准写法(无锁化设计核心)
        int available = getState(); // volatile读保证可见性
        
        // 资源计算:
        // 1. available:当前可用资源数量
        // 2. acquires:请求获取的数量
        // 3. remaining:剩余资源数量
        int remaining = available - acquires;
        
        // 返回值处理逻辑:
        // 1. remaining < 0:资源不足,返回负数触发排队等待
        // 2. compareAndSetState成功:原子更新资源状态,返回剩余量
        if (remaining < 0 || compareAndSetState(available, remaining)) {
            return remaining; // 返回剩余资源数(共享模式关键设计点)
        }
        // CAS失败则继续自旋重试(典型无锁编程模式)
    }
}

/**
 * 原子化释放信号量资源的核心实现:
 * - 使用CAS自旋确保并发安全
 * - 返回true将触发doReleaseShared唤醒传播机制
 */
protected boolean tryReleaseShared(int releases) {
    for (;;) { // 自旋重试保证原子性
        int current = getState(); // volatile读取当前状态
        
        // 资源增加操作:
        // 1. current:当前可用资源数量
        // 2. releases:要释放的资源数
        // 3. next:新资源总量
        int next = current + releases;
        
        // 状态一致性保障:
        // 1. CAS确保状态变更的原子性
        // 2. 失败则重新自旋重试
        if (compareAndSetState(current, next)) {
            // 成功释放后总是返回true,这会触发:
            // 1. doReleaseShared 唤醒后继节点
            // 2. 实现资源释放后的传播唤醒机制
            return true;
        }
        // CAS失败则继续自旋(保持无锁化设计优势)
    }
}

四、设计精髓总结

  1. 状态抽象:通过state字段的灵活语义支持不同同步场景
  2. 无锁化设计:CAS+自旋减少线程阻塞
  3. 传播唤醒机制:避免单次唤醒导致的性能瓶颈
  4. 双端队列管理:高效维护等待线程顺序
  5. 模板方法模式:子类仅需实现核心逻辑