AQS原理

目录

介绍

原理

CLH锁介绍

AQS中CLH锁队列

详细解析

源码分析

资源获取(acquire)

资源释放(release)

条件变量(ConditionObject)

核心流程图

性能优化点

使用场景


介绍

        在Java并发包(Java.util.concurrent)中,AbstractQueuedSynchronizer(简称AQS)是构建锁和同步器的核心框架。像ReentrantLock、CountDownLatch、Semaphore等常见同步工具,底层都依赖于AQS。

        AQS核心功能是资源状态管理(通过一个整数表示同步状态,子类可以定义该状态的意义和操作方式)、线程排队管理(通过 FIFO 等待队列管理多个线程的竞争和等待)、提供模板方法(QS 提供了一系列模板方法,子类可以通过实现这些方法来定义具体的同步机制),AQS 的设计理念是将同步状态和线程队列的管理逻辑抽象出来,以便不同类型的同步器可以重用这些逻辑。这样,开发者可以集中精力于具体的同步器逻辑实现,而无需关心底层的线程排队和状态管理细节。

        在JDK1.5中首次添加AQS,Java并发包的作者是Doug Lea,深入理解juc的相关理论可以参考Doug Lea写的《The_java.util.concurrent_Synchronizer_Framework》论文。

原理

AQS通过两个关键组件管理同步状态:

  • volatile int state:表示共享资源的状态(如锁的重入次数、信号量许可数)。
  • FIFO双向队列:存储等待资源的线程(CLH队列的变体)。

CLH锁介绍

        CLH锁其实是基于队列(单向链表)排队的自旋锁,申请加锁的线程首先会通过CAS操作在单向链表的尾部增加一个节点,之后该线程只需要在其前驱节点上进行普通自旋,等待前驱节点释放锁即可。

AQS中CLH锁队列

与传统CLH锁不同处:

  • 自旋+阻塞:普通CLH锁使用自旋等待锁释放,这样会占用CPU资源,AQS中短暂自旋后进入阻塞状态,等待被唤醒,减少CPU占用。
  • 双向队列:AQS采用双向链表存储,节点不仅知道前驱节点,也可以唤醒后继节点,简化了队列操作,提高唤醒效率。

详细解析

原理图:

        AQS通过把线程封装为Node节点,建立节点之间的关联关系来实现锁分配的,因为没有定义使用真实队列实例,所以称为虚拟的双向队列。以下是其定义(使用了jdk22中定义,jdk8中Node节点状态定义为waitStatus,且有一些状态值定义,此处不进行罗列):

// Head of the wait queue, lazily initialized
private transient volatile Node head;
// Tail of the wait queue. After initialization, modified only via casTail.
private transient volatile Node tail;
// The synchronization state.
private volatile int state;

// 节点状态标志位
static final int WAITING   = 1;     // 等待唤醒
static final int CANCELLED = 0x80000000; // 已取消
static final int COND      = 2;     // 位于条件队列


// Node节点定义
abstract static class Node {
        volatile Node prev;       // initially attached via casTail
        volatile Node next;       // visibly nonnull when signallable
        Thread waiter;            // visibly nonnull when enqueued
        volatile int status;      // written by owner, atomic bit ops by others
        ......
}

// state状态操作方法
protected final int getState() {
    return state;
}
protected final void setState(int newState) {
    state = newState;
}
protected final boolean compareAndSetState(int expect, int update) {
    return U.compareAndSetInt(this, STATE, expect, update);
}

// 对外提供的模板方法
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}
protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}
protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}
protected boolean tryReleaseShared(int arg) {
    throw new UnsupportedOperationException();
}
protected boolean isHeldExclusively() {
    throw new UnsupportedOperationException();
}

        AQS中线程的挂起与唤起是通过LockSupport.park()和LockSupport.unpark()实现的。AQS提供两种资源共享方式,分别是独占式和共享式。

  • 独占式资源访问:同一时刻仅一个线程能获取资源(如 ReentrantLock)。
  • 共享式资源访问:允许多个线程同时获取资源(如 SemaphoreCountDownLatch)。

源码分析

        以下基于jdk22,实现与jdk8版本略有不同。

资源获取(acquire)

final int acquire(AbstractQueuedSynchronizer.Node node, int arg, boolean shared,
                  boolean interruptible, boolean timed, long time) {
    Thread current = Thread.currentThread();
    byte spins = 0, postSpins = 0;   // retries upon unpark of first thread
    boolean interrupted = false, first = false;
    AbstractQueuedSynchronizer.Node pred = null;               // predecessor of node when enqueued

    /*
     * Repeatedly:
     *  Check if node now first
     *    if so, ensure head stable, else ensure valid predecessor
     *  if node is first or not yet enqueued, try acquiring
     *  else if queue is not initialized, do so by attaching new header node
     *     resort to spinwait on OOME trying to create node
     *  else if node not yet created, create it
     *     resort to spinwait on OOME trying to create node
     *  else if not yet enqueued, try once to enqueue
     *  else if woken from park, retry (up to postSpins times)
     *  else if WAITING status not set, set and retry
     *  else park and clear WAITING status, and check cancellation
     */

    for (;;) {
        if (!first && (pred = (node == null) ? null : node.prev) != null &&
                !(first = (head == pred))) {
            if (pred.status < 0) {
                cleanQueue();           // predecessor cancelled
                continue;
            } else if (pred.prev == null) {
                Thread.onSpinWait();    // ensure serialization
                continue;
            }
        }
        if (first || pred == null) {
            boolean acquired;
            try {
                if (shared)
                    acquired = (tryAcquireShared(arg) >= 0);
                else
                    acquired = tryAcquire(arg);
            } catch (Throwable ex) {
                cancelAcquire(node, interrupted, false);
                throw ex;
            }
            if (acquired) {
                if (first) {
                    node.prev = null;
                    head = node;
                    pred.next = null;
                    node.waiter = null;
                    if (shared)
                        signalNextIfShared(node);
                    if (interrupted)
                        current.interrupt();
                }
                return 1;
            }
        }
        AbstractQueuedSynchronizer.Node t;
        if ((t = tail) == null) {           // initialize queue
            if (tryInitializeHead() == null)
                return acquireOnOOME(shared, arg);
        } else if (node == null) {          // allocate; retry before enqueue
            try {
                node = (shared) ? new AbstractQueuedSynchronizer.SharedNode() : new AbstractQueuedSynchronizer.ExclusiveNode();
            } catch (OutOfMemoryError oome) {
                return acquireOnOOME(shared, arg);
            }
        } else if (pred == null) {          // try to enqueue
            node.waiter = current;
            node.setPrevRelaxed(t);         // avoid unnecessary fence
            if (!casTail(t, node))
                node.setPrevRelaxed(null);  // back out
            else
                t.next = node;
        } else if (first && spins != 0) {
            --spins;                        // reduce unfairness on rewaits
            Thread.onSpinWait();
        } else if (node.status == 0) {
            node.status = WAITING;          // enable signal and recheck
        } else {
            long nanos;
            spins = postSpins = (byte)((postSpins << 1) | 1);
            if (!timed)
                LockSupport.park(this);
            else if ((nanos = time - System.nanoTime()) > 0L)
                LockSupport.parkNanos(this, nanos);
            else
                break;
            node.clearStatus();
            if ((interrupted |= Thread.interrupted()) && interruptible)
                break;
        }
    }
    return cancelAcquire(node, interrupted, interruptible);
}

关键步骤

  1. 快速尝试:若节点未入队且为头节点后继,直接调用 tryAcquire / tryAcquireShared
  2. 队列初始化:若队列为空,创建哑节点(ExclusiveNode)
  3. 节点入队:将当前线程包装为节点(独占ExclusiveNode/共享SharedNode)
  4. 状态自旋:设置节点状态为 WAITING(等待唤醒)根据中断/超时策略选择阻塞方式(LockSupport.park)
  5. 唤醒后重试:被唤醒后重新尝试获取资源
  6. 资源获取成功:将当前节点设为头节点;断开原头节点链接(辅助GC);共享模式下唤醒后继节点

资源释放(release)

// 独占模式释放
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        signalNext(head); // 唤醒后继节点
        return true;
    }
    return false;
}

// 共享模式释放
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        signalNext(head); // 唤醒后继节点
        return true;
    }
    return false;
}

private static void signalNext(AbstractQueuedSynchronizer.Node h) {
    AbstractQueuedSynchronizer.Node s;
    if (h != null && (s = h.next) != null && s.status != 0) {
        s.getAndUnsetStatus(WAITING);
        LockSupport.unpark(s.waiter);
    }
}

关键操作

  1. signalNext():从后向前遍历,找到首个未取消节点并唤醒
  2. 清除节点WAITING状态,避免重复唤醒

条件变量(ConditionObject)

  • await流程

  1. 创建ConditionNode加入条件队列

  2. 完全释放资源(enableWait()

  3. 阻塞直到被signal或中断

  4. 被唤醒后重新竞争资源

  • signal流程

  1. 将条件队列头节点移入CLH队列

  2. 清除节点COND状态

  3. 设置节点为WAITING状态

  4. 唤醒节点对应线程

核心流程图

性能优化点

  • 延迟初始化:首次竞争时才创建头节点
  • GC优化:出队节点及时断开引用(head.next = null
  • OOME处理:资源不足时退避重试(acquireOnOOME
  • 自旋避让Thread.onSpinWait()优化多核竞争

使用场景

同步器实现模式关键特性
ReentrantLock独占可重入、公平/非公平
Semaphore共享资源计数控制
CountDownLatch共享一次性同步屏障
ReentrantReadWriteLock混合模式读写锁分离
CyclicBarrierReentrantLock + Condition多个线程相互等待,支持回调,可重置
### Java AQS (AbstractQueuedSynchronizer) 原理详解 #### AQS概述 AQS 是一个用于构建锁和其他同步组件的基础框架。此框架的核心思想在于使用单个 `volatile` 整形变量 `state` 来表示同步状态,并通过内置的 FIFO 队列来完成获取和释放同步状态时的排队工作[^3]。 #### 同步状态管理 AQS 使用一个名为 `state` 的私有成员变量来保存同步状态,这个变量被声明为 `volatile` 类型以确保多线程环境下的可见性和有序性。对于不同的同步器而言,`state` 可能代表的意义有所不同。例如,在 `ReentrantLock` 中,`state` 表示的是持有锁的数量;而在 `Semaphore` 中,则可能表示剩余许可数[^5]。 #### 线程竞争处理 当多个线程尝试访问受保护资源时,如果当前线程无法立即获得所需的同步状态(比如因为已经被占用),那么该线程将会加入到由 AQS 维护的一个先进先出(FIFO)队列之中等待。每当某个线程成功释放了其持有的同步状态之后,处于队首位置的那个等待线程便有机会重新尝试获取同步状态并继续执行下去。 #### 自定义同步逻辑实现 为了适应各种各样的需求场景,开发者可以通过继承自 `AbstractQueuedSynchronizer` 并覆盖其中的关键方法来自定义特定类型的同步行为。具体来说: - 对于独占模式下使用的同步器(如 `ReentrantLock`),通常会重载 `tryAcquire(int arg)` 和 `tryRelease(int arg)` 方法; - 而共享模式则适用于允许多个线程同时拥有相同权限的情况(像信号量),此时应着重关注 `tryAcquireShared(int arg)` 和 `tryReleaseShared(int arg)` 这两个函数的编写[^2]。 ```java // 定义一个新的同步器类 public class MySync extends AbstractQueuedSynchronizer { protected boolean tryAcquire(int acquires) { /* ... */ } protected boolean tryRelease(int releases) { /* ... */ } // 如果是共享模式还需要实现如下方法 protected int tryAcquireShared(int acquires) { /* ... */ } protected boolean tryReleaseShared(int releases) { /* ... */ } } ``` #### CAS操作保障原子性 由于存在并发修改的可能性,所以在更改 `state` 的时候必须采取措施防止竞态条件的发生。为此,AQS 利用了硬件层面提供的比较交换指令(CAS Compare And Swap)来进行无锁编程,从而保证了对 `state` 更新过程的安全可靠[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值