AQS源码分析

让我们从详细源码的角度逐步分析 Java 8 中 AbstractQueuedSynchronizer(AQS)的核心方法和工作原理。这里我们将深入讨论 AQS 的关键部分,包括独占模式、共享模式、同步队列、Node 状态转换、锁的获取和释放等内容,详细解析每个核心方法的实现。

1. AQS 的整体设计思路

AQS 通过一个 volatile int 类型的变量 state 来表示同步状态,并通过一个双向链表(同步队列)来管理争用资源的线程。AQS 提供了两种模式:

  • 独占模式:只有一个线程可以获取资源,其他线程需排队等待,常见的例子有 ReentrantLock
  • 共享模式:多个线程可以同时获取资源,常见的例子有 SemaphoreCountDownLatch

2. 同步状态(state

state 是 AQS 的核心,通过 getState()setState(int)compareAndSetState(int, int) 方法来管理同步状态的读写和更新。这些方法分别是读取同步状态、设置同步状态和原子更新同步状态(CAS 操作)。

private volatile int state;

protected final int getState() {
    return state;
}

protected final void setState(int newState) {
    state = newState;
}

protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
  • compareAndSetState() 使用 CAS 来更新同步状态,以保证线程安全性,避免锁竞争。

3. 同步队列(NodeSyncQueue

当一个线程不能获取资源时,它会被封装为 Node 加入到同步队列中进行排队等待。Node 是 AQS 的内部静态类,封装了线程及其状态。

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

    static final int CANCELLED = 1;         // 线程等待超时或中断被取消
    static final int SIGNAL = -1;           // 需要通知后续节点唤醒
    static final int CONDITION = -2;        // 条件等待
    static final int PROPAGATE = -3;        // 共享模式下的传播状态

    volatile int waitStatus;                // 当前节点的状态
    volatile Node prev;                     // 前驱节点
    volatile Node next;                     // 后继节点
    volatile Thread thread;                 // 当前节点对应的线程
    Node nextWaiter;                        // 条件等待队列中的下一个节点
}
  • CANCELLED:当线程因为超时或中断取消等待时,节点的 waitStatus 会变为 CANCELLED
  • SIGNAL:表示该节点的后继节点处于等待状态,需要通知后继节点来继续竞争资源。
  • CONDITION:当节点处于 Condition 等待队列时,状态为 CONDITION,即线程在 Condition.await() 后进入等待状态。
  • PROPAGATE:仅在共享模式下使用,表示资源的释放需要传播。

4. 获取资源流程(独占模式)

在独占模式下,线程通过调用 acquire(int) 来获取资源。AQS 首先会尝试直接获取资源,如果失败,线程会被加入到等待队列中并阻塞,直到资源可用。

4.1 acquire(int arg)
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
  • tryAcquire(arg):这是一个模板方法,由具体的同步器(例如 ReentrantLock)实现,尝试获取锁。如果获取失败,返回 false
  • addWaiter(Node.EXCLUSIVE):如果 tryAcquire 失败,当前线程会被封装为一个 Node 并加入同步队列。
  • acquireQueued(Node, int):线程在同步队列中等待,当前驱节点释放资源时,才会被唤醒重新尝试获取锁。
4.2 addWaiter(Node mode)
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)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}
  • 该方法将线程封装为一个 Node 并添加到同步队列的尾部。如果 tail 不为 null,线程尝试直接插入队列尾部;如果失败,则调用 enq(Node) 进行入队操作。
4.3 acquireQueued(Node node, int arg)
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; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
  • 该方法会不断检查前驱节点是否为头结点,并尝试获取锁。如果成功,当前线程会成为新的头结点。否则,线程进入阻塞状态等待唤醒。

  • shouldParkAfterFailedAcquire(Node p, Node node):判断当前线程是否需要进入阻塞状态。

  • parkAndCheckInterrupt():将当前线程阻塞,并在被唤醒时检查是否中断。

4.4 shouldParkAfterFailedAcquire(Node pred, Node node)
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        return true;
    if (ws > 0) {
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
  • 判断前驱节点是否处于 SIGNAL 状态,如果是,则说明当前线程需要阻塞;否则,更新前驱节点的状态为 SIGNAL,表示需要唤醒后继节点。
4.5 parkAndCheckInterrupt()
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}
  • 该方法调用 LockSupport.park(this) 来将当前线程阻塞,并在被唤醒时检查线程是否被中断。

5. 释放资源流程(独占模式)

当持有锁的线程调用 release(int) 时,它会释放资源,并唤醒等待队列中的下一个线程。

5.1 release(int arg)
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
  • tryRelease(arg):模板方法,具体的同步器需要实现该方法释放资源。
  • unparkSuccessor(Node):唤醒同步队列中的下一个线程。
5.2 unparkSuccessor(Node node)
private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    Node s = node.next;
    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;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}
  • 该方法唤醒队列中的下一个等待节点。通过 LockSupport.unpark(Thread) 唤醒线程,使其继续执行。

6. 共享模式下的锁获取与释放

在共享模式下,多个线程可以同时获取锁。AQS 通过 acquireShared(int)releaseShared(int) 来管理共享资源的访问。

6.1 acquireShared(int arg)
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}
  • tryAcquireShared(int):尝试获取共享资源,具体实现由子类提供。返回值小于 0 表示获取失败。
  • doAcquireShared(int):当获取失败时,线程会进入同步队列等待。
6.2 doAcquireShared(int arg)
private void doAcquireShared(int arg) {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
  • 如果前驱节点为头结点,当前线程尝试获取资源(tryAcquireShared),如果获取成功,设置为头结点并传播获取结果。

7. 总结

AbstractQueuedSynchronizer(AQS)通过一个状态变量 state 和一个等待队列,提供了灵活的并发控制机制。无论是独占模式还是共享模式,线程都通过等待队列排队,竞争获取锁。当资源被释放时,等待队列中的线程依次被唤醒继续执行。

AQS 通过模板方法模式将资源的具体获取与释放逻辑交给具体的同步器实现(如 ReentrantLockSemaphore 等),而自身负责提供统一的线程管理、队列维护以及状态控制功能。

这种设计使得 AQS 成为 Java 并发包中的基础同步工具,极大提高了锁的实现效率和灵活性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

愤怒的代码

如果您有受益,欢迎打赏博主😊

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值