java并发:AQS源码深度解读

AQS是 AbstractQueuedSynchronizer 的简称,即抽象队列同步器,从字⾯意思上理解:

抽象:抽象类,只实现⼀些主要逻辑,有些⽅法由⼦类实现;

队列:使⽤先进先出(FIFO)队列存储数据;

同步:实现了同步的功能。

 

AQS是⼀个⽤来构建锁和同步器的框架,使⽤AQS能简单且⾼效地构造出应⽤⼴泛的同步器,⽐如ReentrantLock、ReentrantReadWriteLock、Semaphore等皆是基于AQS的。 

image

 

AbstractQueuedSynchronizer 继承了抽象类 AbstractOwnableSynchronizer

image

解读:ReentrantLock 通过 exclusiveOwnerThread 记录持有者

 

Semaphore 的内部类 Sync 继承了 AbstractQueuedSynchronizer(ReentrantLock 也有类似实现)

image

 

AQS内部使⽤了⼀个用volatile标识的变量state来作为资源的标识,state 变量是实现同步状态控制的关键【见后文State章节详述】

image

解读:

compareAndSetState的实现依赖于Unsafe的compareAndSwapInt()⽅法。

 

AQS类本身实现的是⼀些排队和阻塞的机制,⽐如线程等待队列的维护(如获取资源失败⼊队/唤醒出队等);AQS是一个FIFO的双向队列,其内部通过head和tail记录队首和队尾元素,队列元素的类型为 Node。

 

 

 

image

 

Node

Node 中的 SHARED 用来标记该线程是获取共享资源时被阻塞挂起后放入 AQS 队列的;EXCLUSIVE 用来标记该线程是获取独占资源时被阻塞挂起后放入 AQS 队列的;⼀般情况下,⼦类只需要根据需求实现其中⼀种模式,当然也有同时实现两种模式的同步类,如 ReadWriteLock。

Node 中的 prev 记录当前节点的前驱节点;next 记录当前节点的后继节点;thread变量用来存放进入 AQS 队列的线程;

waitStatus 记录当前线程等待状态,可以为:CANCELLED (线程被取消了)、SIGNAL (线程需要被唤醒)、CONDITION (线程在条件队列里面等待)、PROPAGATE (释放共享资源时需要通知其他节点);

nextWaiter实现Condition条件上的等待线程 队列(单向队列)

image

 

ConditionObject

AQS 的内部类 ConditionObject 结合锁实现线程同步(实现了条件变量的功能);ConditionObject 可以直接访问 AQS对象内部的变量,比如 state 和 AQS 队列。

关于ConditionObject的设计:等待队列是条件变量特有的,每个Condition实例独立维护一个队列,存放调用await()的线程;而同步队列是AQS全局的,存放等待锁的线程。

名词解释

1. 同步队列(Sync Queue)

作用:
同步队列是 AQS 的核心数据结构,用于管理竞争锁的线程。当线程尝试获取锁失败时,会被封装为 Node 节点并加入同步队列尾部,进入阻塞状态(LockSupport.park())。
特点:

全局唯一:每个 AQS 实例只有一个同步队列。
基于 CLH 变体:通过双向链表实现,支持公平锁和非公平锁的调度。

2. 等待队列(Wait Queue)

作用:
等待队列是 ConditionObject 的内部数据结构,用于管理等待特定条件的线程。当线程调用 condition.await() 时,会释放锁并加入等待队列。
特点:

每个 ConditionObject 实例独立维护一个等待队列(单向链表)。
线程在等待队列中不参与锁竞争,直到被 signal() 唤醒。

协同工作的时序

—— 线程从同步队列获取锁,在条件不满足时进入等待队列,被signal后回到同步队列重新竞争锁。

 

 

 

image

 

Condition接口

ConditionObject 实现了Condition接口(java.util.concurrent.locks.Condition):

image

 

await() 操作:
将当前线程加入等待队列,并释放锁(否则其他线程无法获取锁修改条件)。

伪代码:

public final void await() throws InterruptedException {
    // 1. 创建新节点加入等待队列尾部
    Node node = addConditionWaiter();
    // 2. 完全释放锁(修改 state 值)
    int savedState = fullyRelease(node);
    // 3. 阻塞线程,等待 signal
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
    }
    // 4. 被唤醒后重新竞争锁
    acquireQueued(node, savedState);
}

 

signal() 操作:
将等待队列中的线程移回 AQS 的同步队列,使其可竞争锁。

伪代码:

public final void signal() {
    // 1. 检查当前线程是否持有锁
    if (!isHeldExclusively()) throw new IllegalMonitorStateException();
    // 2. 取出等待队列首节点
    Node first = firstWaiter;
    if (first != null) {
        // 3. 将节点从等待队列移至同步队列
        doSignal(first);
    }
}

关键步骤:

校验锁状态:确保当前线程持有锁(否则抛出异常)。
转移节点:

—— 将等待队列的首节点移出,将节点的 waitStatus 从 CONDITION(-2)改为 0(初始状态)。
—— 调用 enq() 方法将其加入同步队列尾部。
—— 设置前驱节点状态:将新节点的前驱节点状态设为 SIGNAL(-1),(表示需唤醒后续节点)。

唤醒线程:当持有锁的线程调用 unlock() 时,会检查头节点的 waitStatus,若为 SIGNAL,则调用 unparkSuccessor() 唤醒其后继节点(即被转移的线程,通过 LockSupport.unpark() 唤醒线程);唤醒后,该线程在 acquireQueued() 中尝试获取锁。

Note:

唤醒操作由前驱节点释放锁时触发,而非 signal() 直接唤醒

伪代码:

public void unlock() {
    int state = getState() - 1;
    if (state == 0) { // 完全释放锁
        setExclusiveOwnerThread(null);
        Node head = getHead();
        if (head != null && head.waitStatus != 0) {
            unparkSuccessor(head); // 唤醒后继节点
        }
    }
}

问题:为何不立即唤醒?
此设计解决了 线程安全 和 性能 问题:

避免无效竞争:若 signal() 后立即唤醒,但锁仍被当前线程持有,被唤醒线程会再次阻塞(浪费资源)。

确保条件可见性:延迟唤醒保证被唤醒线程在获取锁后,能直接观察到最新的条件状态(其他线程已修改)。

减少上下文切换:降低线程切换频率。

 

时序图:

image

  

ConditionObject 是条件变量,每个条件变量在内部维护了一个条件队列 (单向链表队列),这个条件队列和 AQS 队列不是一回事。

此处的队列是用来存放调用条件变量的 await 方法后被阻塞的线程,队列的头、尾元素分别为 firstWaiter 和 lastWaiter。

Note:

调用条件变量的 await()方法就相当于调用共享变量的 wait()方法,调用条件变量的 signal方法就相当于调用共享变量的 notify()方法,调用条件变量的 signalAll ( )方法就相当于调用共享变量的 notifyAll()方法。

至此,相信大家都已经知道条件变量是什么了,它能用来做什么。 

 

条件变量示例

示例中(2)处使用Lock 对象的 newCondition ()方法创建了一个 ConditionObject 变量(参见后续ReentrantLock源码截图),该变量就是 Lock锁对应的一个条件变量。

示例中(3)处获取了独占锁,示例中(4)处则调用了条件变量的 await ()方法阻塞挂起了当前线程。

当其他线程调用条件变量的 signal方法时,被阻塞的线程才会从 await处返回。

image

image

 

newCondition()方法

image

 

Note:

在调用条件变量的 signal 和 await方法前必须先获取条件变量对应的锁,如果在没有获取到锁之前调用了条件变量的 await方法则会抛出 java.lang.IllegalMonitorStateException异常。

sequenceDiagram
    Thread A->>+Lock: lock()
    Thread A->>+Condition: await()
    Condition->>Lock: fullyRelease() // 释放锁
    Thread B->>+Lock: lock()
    Thread B->>Condition: signal()
    Condition->>AQS: transferWaiter() // 线程A移入同步队列
    Thread B->>Lock: unlock()
    Thread A->>Lock: re-acquire lock() // 重新竞争锁

说明:

调用 await/signal 前未持有锁,await() 无法释放未持有的锁 → 死锁;signal() 无法访问私有等待队列 → 抛出 IllegalMonitorStateException

 

与 synchronized 的对比

image

 

一个 Lock对象可以创建多个条件变量。

AQS 只提供了 ConditionObject 的实现,并没有提供 newCondition 函数,该函数用来 new 一个 ConditionObject对象;需要由 AQS 的子类来实现 newCondition 函数。 

 

小结:

下图反映了前面描述的关系:

 

详解:

当多个线程同时调用 lock.lock()方法获取锁时,只有一个线程获取到了锁,其他线程会被转换为 Node 节点插入到 lock 锁对应的 AQS 阻塞队列里面,并做自旋 CAS 尝试获取锁。

如果获取到锁的线程调用了对应条件变量的 await()方法,则该线程会释放获取到的锁,并被转换为 Node 节点插入到条件变量对应的条件队列里面。

此时因调用 lock.lock() 方法被阻塞到 AQS 队列里面的一个线程会获取到被释放的锁,如果该线程也调用了条件变量的 await ()方法则该线程也会被放入条件变量的条件队列里面。

当另外一个线程调用条件变量的 signal()或者 signalAll()方法时,会把条件队列里面的一个或者全部 Node节点移动到 AQS 的阻塞队列里面,等待时机获取锁。

 

设计优势:

解耦锁与条件:通过分离同步队列(锁竞争)和等待队列(条件等待),支持多个条件变量。
避免虚假唤醒:线程被 signal() 唤醒后需重新获取锁,确保条件检查在持有锁时进行(while (condition) 循环)。
高效唤醒:signal() 仅转移节点,由同步队列管理锁竞争,减少不必要的线程切换。

state

在 AQS 中维持了一个状态值 state,可以通过 getState、setState、compareAndSetState 函数修改其值,代码如下:

    /**
     * The synchronization state.
     */
    private volatile int state;

    /**
     * Returns the current value of synchronization state.
     * This operation has memory semantics of a {@code volatile} read.
     * @return current state value
     */
    protected final int getState() {
        return state;
    }

    /**
     * Sets the value of synchronization state.
     * This operation has memory semantics of a {@code volatile} write.
     * @param newState the new state value
     */
    protected final void setState(int newState) {
        state = newState;
    }

    /**
     * Atomically sets synchronization state to the given updated
     * value if the current state value equals the expected value.
     * This operation has memory semantics of a {@code volatile} read
     * and write.
     *
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful. False return indicates that the actual
     *         value was not equal to the expected value.
     */
    protected final boolean compareAndSetState(int expect, int update) {
        return STATE.compareAndSet(this, expect, update);
    }

对于 AQS 来说,线程同步的关键是对 state 进行操作。

state 变量的核心设计

数据类型:volatile int

通过 volatile 保证多线程间的可见性,确保状态变更对所有线程立即可见。
使用 int 类型支持灵活的位运算(如读写锁的高低位拆分)。


访问控制:

通过 getState()、setState()、compareAndSetState() 方法操作,避免直接访问字段。
compareAndSetState() 基于 CAS(Compare-And-Swap) 实现原子更新,确保并发修改的安全性(底层调用 Unsafe 类)。

state 在不同同步器中的语义

state 的具体含义由子类自定义,常见应用场景:
(1)ReentrantLock(可重入锁)

语义:记录当前线程的重入次数

state = 0:锁未被占用。
state = N(N > 0):锁被占用,且当前线程重入了 N 次。


加锁逻辑:

protected boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    } else if (current == getExclusiveOwnerThread()) {
        setState(c + acquires); // 重入时直接累加
        return true;
    }
    return false;
}

 

(2)Semaphore(信号量)

语义:表示剩余可用许可数

state = K:初始许可数量为 K。
每次 acquire() 将 state 减 1,release() 加 1。

 

(3)CountDownLatch(倒计时闩)

语义:未完成的计数任务数

初始化时 state = N(N 为任务数)。
countDown() 调用 compareAndSetState 递减至 0,触发阻塞线程唤醒。

 

(4)ReentrantReadWriteLock(读写锁)

语义:高 16 位表示读锁数量,低 16 位表示写锁重入次数

通过位运算拆分状态:

static final int SHARED_SHIFT = 16;
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
static int exclusiveCount(int c) { return c & (1 << SHARED_SHIFT) - 1; }

 

操作 state 的线程安全机制

CAS 原子性:
所有状态变更通过 compareAndSetState() 实现,避免锁竞争(如 ReentrantLock 非公平模式直接抢锁)。
重入性支持:
通过检查当前线程是否持有锁(getExclusiveOwnerThread())实现重入计数。

 

避免直接修改:
必须通过 AQS 提供的 protected 方法(如 setState())修改状态,否则破坏同步逻辑。

 

state 变量的操作方式(独占与共享)

根据 state 是否属于一个线程,操作 state 的方式分为独占方式和共享方式。

解释:

在 AQS(AbstractQueuedSynchronizer)框架中,state 变量的操作方式(独占与共享) 本质上是根据资源访问的排他性来区分的,而非字面意义上的“属于某个线程”。

在独占模式下state确实被某个线程独占,但共享模式下state不属于任何线程,而是被多个线程共同维护的共享计数器;这个本质差异决定了两种模式在API设计、线程交互和唤醒策略上的所有区别。

 

补充:

独占模式:减少唤醒开销(每次只唤醒一个线程)。
共享模式:降低线程阻塞率(许可充足时无需入队)。

在实现层面共享模式其实复用独占模式的基础设施:共享模式的doAcquireShared方法内部仍然调用了addWaiter(Node.SHARED)来创建共享节点,说明共享模式是构建在独占模式的队列机制之上的扩展。

子类重写的方法不同(tryAcquire vs tryAcquireShared),但底层操作state的CAS机制是完全相同的,这个统一性反映了AQS作为框架的抽象能力。

 

独占方式

核心特征:资源同一时刻仅允许一个线程持有。

—— 使用独占方式获取的资源是与具体线程绑定的,也就是说如果一个线程获取到了资源,则进行标记,其他线程尝试操作 state 获取资源时会发现当前该资源的持有者不是自己,于是在获取失败后被阻塞。

—— 通过 AQS 的 exclusiveOwnerThread 字段(在 AbstractOwnableSynchronizer 中定义)记录持有者,与 state 配合实现重入性。

image

 

image

 

image

 

例子:独占锁 ReentrantLock 的实现

当一个线程获取了ReentrantLock的锁后,在 AQS 内部会使用 CAS 操作把状态值 state 从0 变为 1,然后设置当前锁的持有者为当前线程,当该线程再次获取锁时发现它就是锁的持有者,则把状态值从 1 变为 2,也就是设置可重入次数,而当另外一个线程获取锁时发现自己不是该锁的持有者就会被放入 AQS 阻塞队列后挂起。

在独占方式下获取和释放资源的方法为:

    /**
     * Acquires in exclusive mode, ignoring interrupts.  Implemented
     * by invoking at least once {@link #tryAcquire},
     * returning on success.  Otherwise the thread is queued, possibly
     * repeatedly blocking and unblocking, invoking {@link
     * #tryAcquire} until success.  This method can be used
     * to implement method {@link Lock#lock}.
     *
     * @param arg the acquire argument.  This value is conveyed to
     *        {@link #tryAcquire} but is otherwise uninterpreted and
     *        can represent anything you like.
     */
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

    /**
     * Acquires in exclusive mode, aborting if interrupted.
     * Implemented by first checking interrupt status, then invoking
     * at least once {@link #tryAcquire}, returning on
     * success.  Otherwise the thread is queued, possibly repeatedly
     * blocking and unblocking, invoking {@link #tryAcquire}
     * until success or the thread is interrupted.  This method can be
     * used to implement method {@link Lock#lockInterruptibly}.
     *
     * @param arg the acquire argument.  This value is conveyed to
     *        {@link #tryAcquire} but is otherwise uninterpreted and
     *        can represent anything you like.
     * @throws InterruptedException if the current thread is interrupted
     */
    public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

 

    /**
     * Releases in exclusive mode.  Implemented by unblocking one or
     * more threads if {@link #tryRelease} returns true.
     * This method can be used to implement method {@link Lock#unlock}.
     *
     * @param arg the release argument.  This value is conveyed to
     *        {@link #tryRelease} but is otherwise uninterpreted and
     *        can represent anything you like.
     * @return the value returned from {@link #tryRelease}
     */
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

操作逻辑:

获取资源时,通过 tryAcquire() 修改 state,确保仅一个线程成功(如 state 从 0 变为 1)。
释放资源时,通过 tryRelease() 将 state 回退(如 state 从 N 变为 N-1)。

共享方式

核心特征:资源允许多个线程同时访问(数量由 state 控制)。

—— 使用共享方式获取资源与具体线程不相关;当多个线程去请求资源时通过 CAS 方式竞争。

当一个线程获取到资源后,另一个线程尝试获取资源时,如果当前资源能满足它的需要,则当前线程只需要使用 CAS 方式进行获取即可。

 

例子:Semaphore 信号量

当一个线程通过 acquire()方法获取信号量时,会首先看当前信号量个数是否满足需要,不满足则把当前线程放入阻塞队列,如果满足则通过自旋 CAS 获取信号量。

在共享方式下获取和释放资源的方法为:

    /**
     * Acquires in shared mode, ignoring interrupts.  Implemented by
     * first invoking at least once {@link #tryAcquireShared},
     * returning on success.  Otherwise the thread is queued, possibly
     * repeatedly blocking and unblocking, invoking {@link
     * #tryAcquireShared} until success.
     *
     * @param arg the acquire argument.  This value is conveyed to
     *        {@link #tryAcquireShared} but is otherwise uninterpreted
     *        and can represent anything you like.
     */
    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

    /**
     * Acquires in shared mode, aborting if interrupted.  Implemented
     * by first checking interrupt status, then invoking at least once
     * {@link #tryAcquireShared}, returning on success.  Otherwise the
     * thread is queued, possibly repeatedly blocking and unblocking,
     * invoking {@link #tryAcquireShared} until success or the thread
     * is interrupted.
     * @param arg the acquire argument.
     * This value is conveyed to {@link #tryAcquireShared} but is
     * otherwise uninterpreted and can represent anything
     * you like.
     * @throws InterruptedException if the current thread is interrupted
     */
    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

 

    /**
     * Releases in shared mode.  Implemented by unblocking one or more
     * threads if {@link #tryReleaseShared} returns true.
     *
     * @param arg the release argument.  This value is conveyed to
     *        {@link #tryReleaseShared} but is otherwise uninterpreted
     *        and can represent anything you like.
     * @return the value returned from {@link #tryReleaseShared}
     */
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

操作逻辑:

获取资源时,通过 tryAcquireShared() 原子性减少 state(如 state 从 K 变为 K-1)。
释放资源时,通过 tryReleaseShared() 增加 state(如 state 从 K 变为 K+1)。

 

小结

AQS的设计是基于模板⽅法模式的,它有⼀些⽅法必须要⼦类去实现的,AQS并没有提供可用的tryAcquire和tryRelease方法。

tryAcquire和 tryRelease需要由具体的子类来实现。

子类在实现 tryAcquire和 tryRelease时要根据具体场景使用 CAS算法尝试修改 state状态值, 成功则返回 true,否则返回 false。

子类还需要定义在调用 acquire 和 release 方法时状态值 state 的增减代表什么含义。 

例子:

继承自 AQS 实现的独占锁 ReentrantLock,定义当 status 为 0 时表示锁空闲,为 1 时表示锁己经被占用。

故其在重写 tryAcquire 时,需要使用 CAS 算法查看当前 state 是否为 0,如果为 0 则使用 CAS 设置为 1,并设置当前锁的持有者为当前线程,而后返回true;如果 CAS 失败则返回 false。

 

同理,tryAcquireShared 和 tryReleaseShared 也需要由具体的子类来实现。

例子:

继承自 AQS 实现的读写锁 ReentrantReadWriteLock,读锁在重写 tryAcquireShared 时,首先查看写锁是否被其他线程持有,如果是则直接返回 false; 否则使用 CAS 递增 state 的高 16 位。

(在 ReentrantReadWriteLock 中, state 的高 16 位为获取读锁的次数) 

 

基于 AQS 实现的锁除了需要重写上面介绍的方法外,还需要重写 isHeldExclusively 方法,来判断锁是被当前线程独占还是被共享。

image

 


问题:带有 Interruptibly关键字的函数和不带该关键字的函数有什么区别? 

不带 Intenuptibly 关键字的方法意思是不对中断进行响应。

详解:线程在调用不带 Interruptibly 关键字的方法获取资源时或者获取资源失败被挂起时,其他线程中断了该线程,则该线程不会因为被中断而抛出异常,它还是继续获取资源或者被挂起,也就是说不对中断进行响应,忽略中断。

带 Interruptibly关键字的方法要对中断进行响应。

详解:线程在调用带 Interruptibly 关键字的方法获取资源时或者获取资源失败被挂起时,其他线程中断了该线程,则该线程抛出 InterruptedException 异常而返回。

posted @ 2021-08-12 08:46  时空穿越者  阅读(87)  评论(0)    收藏  举报