AbstractQueuedSynchronizer 原理

AbstractQueuedSynchronizer(简称 AQS)是 Java 并发包中的核心组件之一,它为实现依赖于同步状态的锁和同步器提供了一个框架。像 ReentrantLockCountDownLatchSemaphore 等都是基于 AQS 实现的。


一、概述

  1. 同步状态 (state)
    通过 volatile int state 表示资源状态(如 ReentrantLock 中 0=未锁定,>0=锁定次数)

  2. CLH 变体队列
    双向链表构成的 FIFO 线程等待队列,节点保存阻塞线程和等待状态

  3. 模板方法模式
    子类只需实现 tryAcquire/tryRelease 等tryXXX状态控制方法


二、 源码

1. 核心字段

// AQS 核心字段
private transient volatile Node head;    // 队列头节点(哨兵)
private transient volatile Node tail;    // 队列尾节点
private volatile int state;              // 同步状态

2. 静态内部类 Node

static final class Node {
    // 表示共享模式
    static final Node SHARED = new Node();
    // 表示独占模式
    static final Node EXCLUSIVE = null;

    // 线程等待状态字段
    volatile int waitStatus;

    // 前驱节点
    volatile Node prev;

    // 后继节点
    volatile Node next;

    // 关联的线程
    volatile Thread thread;

    // 下一个等待者
    Node nextWaiter;
}

waitStatus 有以下几种状态:

  • CANCELLED (1):线程已取消。
  • SIGNAL (-1):后续线程需要被唤醒。
  • CONDITION (-2):节点在条件队列中。
  • PROPAGATE (-3):适用于共享模式,传播唤醒。

三、方法分类

AQS 提供了两组 API:

1. 模板方法(由 AQS 实现)

  • acquire(int arg):独占式获取同步状态。
  • release(int arg):独占式释放同步状态。
  • acquireShared(int arg):共享式获取同步状态。
  • releaseShared(int arg):共享式释放同步状态。

这些方法内部调用钩子方法(需子类实现)

2. 钩子方法(由子类实现)

  • tryAcquire(int arg):独占式获取同步状态。
  • tryRelease(int arg):独占式释放同步状态。
  • tryAcquireShared(int arg):共享式获取同步状态。
  • tryReleaseShared(int arg):共享式释放同步状态。
  • isHeldExclusively():当前同步器是否被当前线程独占。

四、核心方法

acquire(...) 方法

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

// 1. 尝试获取资源(子类实现)
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

// 2. 创建节点并入队
private Node addWaiter(Node mode) {
    Node node = new Node(mode);
    for (;;) {
        Node oldTail = tail;
        if (oldTail != null) {
            node.setPrevRelaxed(oldTail);
            if (compareAndSetTail(oldTail, node)) {
                oldTail.next = node;
                return node;
            }
        } else {
            initializeSyncQueue();
        }
    }
}

// 3. 队列中等待获取资源
final boolean acquireQueued(final Node node, int arg) {
    boolean interrupted = false;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // GC assist
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } catch (Throwable t) {
        cancelAcquire(node);
        throw t;
    }
}

// 4. 检查是否需要阻塞
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL) // -1
        return true;
    if (ws > 0) { // CANCELLED
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        pred.compareAndSetWaitStatus(ws, Node.SIGNAL);
    }
    return false;
}

// 5. 阻塞并检查中断
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

说明

  1. 快速路径尝试

    • 首先调用tryAcquire()(子类实现)尝试直接获取资源
    • 成功则立即返回(无锁操作)
  2. 节点入队

    • 创建EXCLUSIVE节点(包含当前线程)
    • 使用CAS自旋确保线程安全入队
    • 队列为空时初始化虚拟头节点(head≠null保证算法健壮性)
  3. 队列中自旋

在这里插入图片描述

  • 头节点检查优化:仅当头节点后继才尝试获取
  • 状态传播机制
    • 将前驱状态设为SIGNAL(-1)(表示需唤醒)
    • 清理CANCELLED节点保证链表连续
  1. 阻塞控制

    • 调用LockSupport.park()精准挂起线程
    • 唤醒后检查中断状态但不响应(留到获取后处理)
  2. 中断处理

    • 阻塞期间中断只做标记(Thread.interrupted()
    • 最终获取资源后调用selfInterrupt()补发中断

设计

  1. 无锁快速路径tryAcquire无竞争时直接成功
  2. CAS队列操作:避免入队时的全局锁
  3. 有限自旋:至少两次tryAcquire机会(入队前+成为头节点时)
  4. 精准唤醒:仅当waitStatus=SIGNAL时才唤醒
  5. 中断延迟响应:保证锁获取的原子性

确保:

  • 公平性:严格FIFO顺序
  • 活性:取消节点自动清理

流程图

流程图

release(...) 方法

public final boolean release(int arg) {
    if (tryRelease(arg)) {          // 1. 尝试释放资源
        Node h = head;              // 2. 获取头节点
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);     // 3. 唤醒后继节点
        return true;
    }
    return false;
}

// 子类实现的资源释放逻辑
protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

// 唤醒后继节点
private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)  // 清除SIGNAL状态
        node.compareAndSetWaitStatus(ws, 0);
    
    Node s = node.next;
    // 1. 直接后继无效时,从尾向前查找
    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;  // 找到第一个未取消的节点
    }
    
    // 2. 唤醒线程
    if (s != null)
        LockSupport.unpark(s.thread);
}

说明

  1. 资源释放阶段

在这里插入图片描述

  • tryRelease() 由子类实现资源释放逻辑
  • 返回 true 表示资源完全释放(如 state=0
  1. 唤醒条件检查

在这里插入图片描述

  • waitStatus != 0 表示存在需要唤醒的后继
  • 常见状态:SIGNAL(-1) 表示后继需要被唤醒
  1. 唤醒算法

在这里插入图片描述

  • 从尾向前查找 解决并发入队时的竞争问题:
    • 新节点入队时:tail CAS -> prev 设置 -> next 设置
    • 保证 prev 链总是完整的,避免 next 链临时断裂
  1. 状态清除机制

在这里插入图片描述

  • 清除 SIGNAL 状态避免重复唤醒
  • 使用 CAS 保证原子性操作

设计

  1. 级联唤醒避免
    仅唤醒直接后继,避免不必要的线程唤醒风暴

  2. 取消节点跳过
    自动过滤 CANCELLED 节点(waitStatus>0

  3. 双向遍历保障
    从尾向前的遍历保证总能找到有效节点:

    for (Node t = tail; t != null && t != node; t = t.prev)
        if (t.waitStatus <= 0)
            s = t;  // 最后一个有效节点
    
  4. 无锁唤醒机制
    LockSupport.unpark() 是轻量级精准唤醒操作:

    • 若线程未阻塞,设置"许可"标记
    • 若线程已阻塞,立即解除阻塞状态

唤醒后的线程生命周期

在这里插入图片描述

注意:被唤醒的线程会在 acquireQueued 方法中继续尝试获取资源,成功后成为新的头节点并继续执行。

流程图

在这里插入图片描述

acquireShared(...) 方法

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)  // 1. 尝试共享获取
        doAcquireShared(arg);       // 2. 获取失败则排队
}

// 子类实现的共享获取逻辑
protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}

// 核心排队逻辑
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;  // GC
                    if (interrupted)
                        selfInterrupt();
                    return;
                }
            }
            // 阻塞控制(与独占模式相同)
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } catch (Throwable t) {
        cancelAcquire(node);
        throw t;
    }
}

// 传播唤醒的核心方法
private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head;  // 记录旧头节点
    setHead(node);  // 设置新头节点
    
    // 传播条件检查(四种情况)
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        if (s == null || s.isShared())
            doReleaseShared();  // 执行共享释放
    }
}

// 共享模式释放
private void doReleaseShared() {
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0))
                    continue;  // CAS失败重试
                unparkSuccessor(h);  // 唤醒后继
            }
            // 设置PROPAGATE状态保证传播继续
            else if (ws == 0 &&
                     !h.compareAndSetWaitStatus(0, Node.PROPAGATE))
                continue;
        }
        if (h == head) break;  // 头节点变化则继续传播
    }
}

说明

  1. 共享获取尝试

    • 子类实现(如 Semaphore 返回 permits-1
    • 返回值 >=0 表示获取成功
  2. 共享节点入队

    • 创建 Node.SHARED 模式节点
    • 入队逻辑与独占模式相同(CAS 添加到尾节点)
  3. 传播机制(核心)

在这里插入图片描述

传播条件(任一满足即传播)

  • propagate > 0(还有剩余资源)
  • 旧头节点状态 <0(含 SIGNAL/PROPAGATE)
  • 新头节点状态 <0
  • 头节点为 null(防御性检查)
  1. 级联唤醒(doReleaseShared)

在这里插入图片描述

  • 通过循环保证唤醒信号持续传播
  • PROPAGATE(-3) 状态确保新请求加入时继续传播

与独占模式的区别

特性共享模式独占模式
节点类型Node.SHAREDNode.EXCLUSIVE
唤醒范围级联唤醒多个线程仅唤醒一个后继
状态传播PROPAGATE 状态机制
典型应用Semaphore, CountDownLatchReentrantLock

设计

  1. 级联唤醒优化
    共享模式成功获取后自动唤醒后续共享节点,提高吞吐量

  2. PROPAGATE 状态解决竞争
    解决极端场景下的信号丢失问题:

    // 场景:线程A释放资源 -> 线程B获取资源 -> 线程C同时请求资源
    else if (ws == 0 &&
             !h.compareAndSetWaitStatus(0, Node.PROPAGATE))
         continue;  // 确保新请求不会丢失唤醒信号
    
  3. 无界资源传播
    当资源充足时(如 Semaphore 许可数很大),一次成功获取可唤醒整个队列

  4. 与独占模式复用队列
    共享节点和独占节点可在同一队列共存,通过 nextWaiter 区分

执行示例(Semaphore)

在这里插入图片描述

注意:当共享节点被唤醒后,会通过 setHeadAndPropagate 继续唤醒后续共享节点,形成级联唤醒效果,这是共享模式高吞吐量的关键设计。

流程图

在这里插入图片描述

releaseShared(...) 方法

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {  // 1. 尝试共享释放
        doReleaseShared();        // 2. 执行共享唤醒
        return true;
    }
    return false;
}

// 子类实现的共享释放逻辑
protected boolean tryReleaseShared(int arg) {
    throw new UnsupportedOperationException();
}

// 共享模式唤醒核心
private void doReleaseShared() {
    for (;;) {  // 自旋循环
        Node h = head;
        if (h != null && h != tail) {  // 队列中有等待节点
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {  // 需要唤醒后继
                // CAS清除SIGNAL状态
                if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0))
                    continue;  // CAS失败重试
                unparkSuccessor(h);  // 唤醒后继
            }
            // 设置PROPAGATE状态保证传播继续
            else if (ws == 0 &&
                     !h.compareAndSetWaitStatus(0, Node.PROPAGATE))
                continue;  // CAS失败重试
        }
        // 检查头节点是否变化(有线程成为新头)
        if (h == head)  // 头节点未变化则退出
            break;
    }
}

说明

  1. 共享资源释放

在这里插入图片描述

  • 子类负责实现状态更新逻辑
  • 返回 true 表示释放成功
  1. 唤醒循环机制

在这里插入图片描述

  • 循环直到头节点稳定不变
  • 处理过程中可能有新线程成为头节点
  1. 状态处理

在这里插入图片描述

  • SIGNAL(-1):立即唤醒后继节点
  • 0状态:设为 PROPAGATE(-3) 确保后续传播
  1. PROPAGATE 状态

在这里插入图片描述

  • 解决极端场景下的信号丢失问题
  • 保证新请求不会错过唤醒信号

设计

  1. 级联传播保证

    for (;;) {
        // ...
        if (h == head) break;  // 关键退出条件
    }
    
    • 只要头节点变化(表示有新线程获取资源),就继续传播
    • 确保释放信号穿透整个队列
  2. 无锁并发控制

    • 所有状态更新使用 CAS 操作
    • compareAndSetWaitStatus 保证原子性
  3. 唤醒效率优化

    • acquireSharedsetHeadAndPropagate 协同工作
    • 共享资源充足时一次性唤醒整个队列
  4. 状态机转换

在这里插入图片描述

与独占模式释放的对比

特性共享模式 (releaseShared)独占模式 (release)
唤醒范围可能唤醒多个后继节点仅唤醒一个后继节点
核心方法doReleaseShared()unparkSuccessor()
状态变化涉及 PROPAGATE 状态仅 SIGNAL 状态
循环机制自旋直到头节点稳定单次执行
典型应用CountDownLatch, SemaphoreReentrantLock

关键
releaseShared() 通过 doReleaseShared() 的自旋循环和状态传播机制,确保共享资源的释放信号能在队列中持续传播,这是共享同步器(如 Semaphore、CountDownLatch)实现"一对多"唤醒的基础。PROPAGATE 状态的引入解决了并发场景下的信号丢失问题。

流程图

在这里插入图片描述


五、总结

特性描述
模板方法模式AQS 提供通用逻辑,子类通过重写钩子方法定制行为
双链表队列FIFO 队列保证公平性,支持并发插入
CAS + volatile实现无锁化队列操作与状态更新
线程阻塞机制使用 LockSupport.park/unpark 控制线程挂起与唤醒

六、共享模式 vs 独占模式

类型示例支持多个线程同时获取
独占模式ReentrantLock
共享模式CountDownLatch、Semaphore
  • 独占模式:一次只能一个线程获取同步状态;
  • 共享模式:允许多个线程同时获取同步状态。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值