AQS 原理与源码深度解析:从面试问到实践

最近面试被虐得很惨,被问到 AQS(AbstractQueuedSynchronizer)原理时,我支支吾吾答不上来。回来后痛定思痛,花了整整三天时间深入研究,终于搞懂了这个 Java 并发编程的核心组件。这篇博客就分享我的学习成果,希望能帮你彻底掌握 AQS。

一、为什么需要 AQS?

先看一个简单的多线程场景:多个线程同时操作一个共享变量,如何保证线程安全?

public class Counter {
    private int count = 0;
    
    public void increment() {
        count++; // 非原子操作,线程不安全
    }
}

我们可以用synchronized关键字:

public synchronized void increment() {
    count++;
}

或者用AtomicInteger

private AtomicInteger count = new AtomicInteger(0);

public void increment() {
    count.incrementAndGet(); // 原子操作
}

这两种方式背后其中的synchronized 就是依赖于 AQS。AQS 是 Java 并发包的基础框架,许多同步工具类如ReentrantLockCountDownLatchSemaphore等都是基于 AQS 实现的。理解 AQS 原理,是掌握 Java 并发编程的关键。

二、AQS 核心思想:CLH 队列锁

AQS 的核心思想是用一个 FIFO 队列来管理线程:

  • 当多个线程竞争同一个锁时,未获取到锁的线程会被放入队列
  • 队列中的线程会按照先来后到的顺序等待获取锁
  • 当锁被释放时,队列中的第一个线程会被唤醒并尝试获取锁

这个队列基于 CLH(Craig, Landin, and Hagersten)锁实现,是一种基于链表的自旋锁。

2.1 队列结构

AQS 内部维护了一个双向链表,每个节点是一个Node对象:

static final class Node {
    // 共享模式标记
    static final Node SHARED = new Node();
    // 独占模式标记
    static final Node EXCLUSIVE = null;
    
    // 线程状态:初始为0,还有CANCELLED(1)、SIGNAL(-1)等
    volatile int waitStatus;
    
    // 前驱节点
    volatile Node prev;
    // 后继节点
    volatile Node next;
    // 等待的线程
    volatile Thread thread;
    
    // 链接到等待条件的下一个节点
    Node nextWaiter;
}

2.2 两种模式

AQS 支持两种锁模式:

  1. 独占模式:同一时间只有一个线程能获取锁,如ReentrantLock
  2. 共享模式:多个线程可以同时获取锁,如SemaphoreCountDownLatch

节点通过nextWaiter字段来标记是共享模式还是独占模式。

三、AQS 核心方法与状态

3.1 核心状态变量

AQS 的核心是一个volatile修饰的整数state,表示同步状态:

private volatile int state;

  • state的含义由具体子类定义,例如在ReentrantLock中,state表示锁的重入次数
  • 通过 CAS(Compare and Swap)操作来原子性地修改state的值

3.2 核心方法

AQS 提供了以下几个关键方法,需要子类去实现:

  1. tryAcquire(int arg):尝试以独占模式获取锁
  2. tryRelease(int arg):尝试以独占模式释放锁
  3. tryAcquireShared(int arg):尝试以共享模式获取锁
  4. tryReleaseShared(int arg):尝试以共享模式释放锁
  5. isHeldExclusively():判断当前锁是否由当前线程独占

这些方法的实现需要依赖state变量,通过 CAS 操作来修改state

3.3 模板方法

AQS 通过模板方法模式定义了一套获取锁和释放锁的框架:

// 独占模式获取锁
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

// 独占模式释放锁
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

// 共享模式获取锁
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

// 共享模式释放锁
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

子类只需要实现前面提到的几个方法,就可以轻松实现一个自定义的同步器。

四、源码解析:以 ReentrantLock 为例

4.1 公平锁与非公平锁

ReentrantLock有公平锁和非公平锁两种实现,我们先看非公平锁的实现:

static final class NonfairSync extends Sync {
    final void lock() {
        if (compareAndSetState(0, 1)) // 尝试直接获取锁
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1); // 调用AQS的acquire方法
    }
    
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

非公平锁会先尝试直接获取锁,如果失败才会进入队列。

4.2 公平锁实现

static final class FairSync extends Sync {
    final void lock() {
        acquire(1); // 直接调用AQS的acquire方法
    }
    
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            // 公平锁会先检查队列中是否有前驱节点
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}

公平锁会先检查队列中是否有前驱节点,只有没有前驱节点时才会尝试获取锁。

4.3 加锁流程

当调用lock()方法时,整个加锁流程如下:

  1. 尝试直接获取锁(非公平锁)
  2. 如果失败,调用 AQS 的acquire()方法
  3. acquire()方法会先调用tryAcquire()方法(由子类实现)
  4. 如果tryAcquire()失败,将当前线程封装成Node节点加入队列
  5. 线程进入阻塞状态,等待被唤醒
  6. 当锁被释放时,队列中的第一个线程会被唤醒并尝试获取锁

4.4 释放锁流程

当调用unlock()方法时:

  1. 调用 AQS 的release()方法
  2. release()方法会先调用tryRelease()方法(由子类实现)
  3. 如果tryRelease()成功,唤醒队列中的下一个线程
  4. 被唤醒的线程尝试获取锁

五、AQS 应用场景

AQS 是 Java 并发包的核心,许多同步工具类都是基于 AQS 实现的:

  1. ReentrantLock:可重入锁,支持公平和非公平模式
  2. CountDownLatch:倒计时锁,多个线程等待,直到计数器为 0
  3. Semaphore:信号量,控制同时访问的线程数量
  4. CyclicBarrier:循环屏障,多个线程相互等待,直到所有线程都到达
  5. ReentrantReadWriteLock:读写锁,允许多个线程同时读,但写时互斥

理解 AQS 原理,有助于我们更好地使用这些工具类,也能帮助我们自定义同步器。

六、面试常见问题

  1. AQS 的核心思想是什么?

    • AQS 的核心思想是用一个 FIFO 队列来管理线程,用一个volatile变量state来表示同步状态
    • 线程竞争锁失败时会被放入队列,等待锁释放时被唤醒
  2. AQS 支持哪两种锁模式?

    • 独占模式(如 ReentrantLock)
    • 共享模式(如 Semaphore、CountDownLatch)
  3. ReentrantLock 的公平锁和非公平锁有什么区别?

    • 非公平锁会先尝试直接获取锁,不管队列中是否有前驱节点
    • 公平锁会先检查队列中是否有前驱节点,只有没有前驱节点时才会尝试获取锁
  4. AQS 的主要方法有哪些?

    • 核心方法:tryAcquire()tryRelease()tryAcquireShared()tryReleaseShared()isHeldExclusively()
    • 模板方法:acquire()release()acquireShared()releaseShared()
  5. 为什么说 AQS 是 Java 并发包的基础?

    • 许多同步工具类都是基于 AQS 实现的,理解 AQS 有助于我们更好地使用这些工具类
    • 掌握 AQS 原理,我们还可以自定义同步器,满足特定的业务需求

七、AQS 的核心数据结构

AQS 的核心是一个双向链表和一个状态变量:

private transient volatile Node head; // 队列头节点
private transient volatile Node tail; // 队列尾节点
private volatile int state; // 同步状态

7.1 Node 节点结构

static final class Node {
    // 共享模式标记
    static final Node SHARED = new Node();
    // 独占模式标记
    static final Node EXCLUSIVE = null;
    
    // 线程状态:初始为0,还有CANCELLED(1)、SIGNAL(-1)、CONDITION(-2)、PROPAGATE(-3)
    volatile int waitStatus;
    
    // 前驱节点
    volatile Node prev;
    // 后继节点
    volatile Node next;
    // 等待的线程
    volatile Thread thread;
    
    // 链接到等待条件的下一个节点,或标记共享模式
    Node nextWaiter;
    
    // 判断是否为共享模式
    final boolean isShared() {
        return nextWaiter == SHARED;
    }
    
    // 获取前驱节点,确保不为null
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }
    
    Node() { // Used to establish initial head or SHARED marker
    }
    
    Node(Thread thread, Node mode) { // Used by addWaiter
        this.nextWaiter = mode;
        this.thread = thread;
    }
    
    Node(Thread thread, int waitStatus) { // Used by Condition
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}

7.2 状态变量

private volatile int state;

// 获取当前状态
protected final int getState() {
    return state;
}

// 设置当前状态
protected final void setState(int newState) {
    state = newState;
}

// 使用CAS设置状态
protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

八、独占模式核心方法

8.1 acquire (int arg) 方法

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

这个方法的逻辑是:

  1. 尝试获取锁(调用tryAcquire
  2. 如果获取失败,将当前线程加入队列(addWaiter
  3. 进入队列后,进入自旋状态尝试获取锁(acquireQueued
  4. 如果在等待过程中被中断,会调用selfInterrupt重新设置中断状态

8.2 tryAcquire (int arg) 方法

这个方法需要子类实现,用于尝试获取锁:

protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

8.3 addWaiter (Node mode) 方法

将当前线程封装成 Node 节点并加入队列尾部:

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方法
    enq(node);
    return node;
}

// 插入节点到队列,如果队列为空,先初始化头节点
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // 队列为空,需要初始化
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

8.4 acquireQueued (final 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);
    }
}

8.5 shouldParkAfterFailedAcquire (Node pred, Node node) 方法

检查获取锁失败后是否应该挂起当前线程:

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        // 前驱节点状态为SIGNAL,表示当前节点可以安全挂起
        return true;
    if (ws > 0) {
        // 前驱节点状态为CANCELLED,跳过所有CANCELLED的节点
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        // 其他状态,将前驱节点状态设为SIGNAL
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

8.6 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;
}

8.7 tryRelease (int arg) 方法

需要子类实现,尝试释放锁:

protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

8.8 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;
        // 从尾部向前查找最前面的一个处于非CANCELLED状态的节点
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread); // 唤醒线程
}

九、共享模式核心方法

9.1 acquireShared (int arg) 方法

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

9.2 tryAcquireShared (int arg) 方法

需要子类实现,尝试以共享模式获取锁:

protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}

9.3 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);
    }
}

9.4 releaseShared (int arg) 方法

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

9.5 tryReleaseShared (int arg) 方法

需要子类实现,尝试以共享模式释放锁:

protected boolean tryReleaseShared(int arg) {
    throw new UnsupportedOperationException();
}

十、条件变量实现

AQS 通过ConditionObject类实现了条件变量:

public class ConditionObject implements Condition, java.io.Serializable {
    private static final long serialVersionUID = 1173984872572414699L;
    private transient Node firstWaiter; // 条件队列的第一个节点
    private transient Node lastWaiter;  // 条件队列的最后一个节点
    
    // 构造方法
    public ConditionObject() { }
    
    // 其他方法...
}

10.1 await () 方法

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    Node node = addConditionWaiter();
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

10.2 signal () 方法

public final void signal() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}

10.3 signalAll () 方法

public final void signalAll() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignalAll(first);
}

十一、总结

通过深入分析 AQS 的原理和源码,我们可以看到:

  1. AQS 的核心是一个 FIFO 队列和一个状态变量,通过 CAS 操作实现原子性
  2. 独占模式和共享模式的实现逻辑基本类似,但在细节上有所不同
  3. 条件变量通过一个单独的条件队列实现,与同步队列相互协作
  4. AQS 提供了一套模板方法,子类只需要实现几个关键方法就可以实现自定义同步器

理解 AQS 的核心原理和源码,对于我们理解 Java 并发包中的各种同步工具类(如 ReentrantLock、CountDownLatch 等)有很大帮助。同时,也能帮助我们在面试中更好地回答 AQS 相关的问题。

希望这篇博客能帮助你彻底掌握 AQS。如果有任何疑问,欢迎在评论区留言讨论!

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

程序员小胡12138

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值