前言

AQS(AbstractQueuedSynchronizer)是 Java 并发包(java.util.concurrent)中的核心基础类,它提供了一个框架来实现阻塞锁和相关的同步器(如信号量、CountDownLatch 等)。AQS 内部使用了一个 FIFO 的双向队列来管理线程,这个队列存储的是等待获取同步状态的线程节点。

一、AQS 的核心原理

1. AQS 的作用是什么?

AQS 是一个用于构建锁和同步器的框架,它通过 FIFO 双向队列 管理等待线程,并通过 volatile int state 变量实现资源状态的原子操作。开发者只需重写少量方法,即可自定义同步工具。

2. AQS 的底层结构

(1) 资源状态(state

  • 操作方式

    • getState():获取当前状态值。
    • setState(int newState):直接设置状态值。
    • compareAndSetState(int expect, int update):通过 CAS 原子更新状态。
  • 实际应用

    • ReentrantLockstate 表示锁的重入次数(0=未锁定,1=已锁定,>1=重入)。
    • Semaphorestate 表示剩余的许可证数量。

(2) 等待队列(CLH 变种)

  • 双向链表实现,每个节点(Node)封装一个等待线程。
  • 节点状态:

    • CANCELLED(线程已取消等待)
    • SIGNAL(后继节点需要被唤醒)
    • CONDITION(线程在条件队列中等待)

3. AQS 的两种模式

(1) 独占模式(Exclusive)

  • 特点:同一时间只有一个线程能获取资源。
  • 应用场景ReentrantLockReentrantReadWriteLock.WriteLock
  • 核心方法

    • acquire(int arg):获取资源,失败则进入队列阻塞。
    • release(int arg):释放资源,唤醒后继节点。

(2) 共享模式(Shared)

  • 特点:多个线程可同时获取资源。
  • 应用场景SemaphoreCountDownLatch
  • 核心方法

    • acquireShared(int arg):共享式获取资源。
    • releaseShared(int arg):共享式释放资源,传播唤醒信号。

4. 如何自定义同步器?

继承 AbstractQueuedSynchronizer 并重写以下方法:

// 独占模式
protected boolean tryAcquire(int arg) { ... }  // 尝试获取资源
protected boolean tryRelease(int arg) { ... }  // 尝试释放资源

// 共享模式
protected int tryAcquireShared(int arg) { ... }  
protected boolean tryReleaseShared(int arg) { ... }

// 是否被当前线程独占
protected boolean isHeldExclusively() { ... }

二、AQS 高频面试题

1. 公平锁与非公平锁的区别?AQS 如何实现?

非公平锁:线程直接尝试 CAS 抢占资源,无需检查等待队列。

优点:减少线程切换,吞吐量高。

缺点:可能导致线程饥饿。

公平锁:线程抢占资源前,先调用 hasQueuedPredecessors() 检查队列中是否有等待线程。

优点:保证先到先得,避免饥饿。

缺点:上下文切换频繁,吞吐量较低。

源码对比:

// 非公平锁(ReentrantLock.NonfairSync)
protected final boolean tryAcquire(int acquires) {
    if (compareAndSetState(0, 1)) { ... } // 直接 CAS 抢锁
}

// 公平锁(ReentrantLock.FairSync)
protected final boolean tryAcquire(int acquires) {
    if (hasQueuedPredecessors()) return false; // 先检查队列
    if (compareAndSetState(0, 1)) { ... }
}

2. 为什么 AQS 使用双向队列而非单向队列?

线程取消的高效处理:当线程因超时或中断取消等待时,需要快速修改前驱和后继节点的指针。

时间复杂度优化:

单向队列删除中间节点需遍历,时间复杂度 O(n)。

双向队列通过 prev 和 next 指针直接操作,时间复杂度 O(1)。

3. AQS 的 ConditionObject 和 Object.wait() 有什么区别?

对比维度ConditionObjectObject.wait()
绑定对象必须绑定到 AQS 实现的锁(如 ReentrantLock)任意 synchronized 对象
多条件队列支持多个条件队列(如生产者-消费者模型分组)仅一个等待队列
唤醒精度signal() 精确唤醒指定条件队列的线程notify() 随机唤醒一个线程
中断支持提供 awaitUninterruptibly() 方法需手动处理中断

4. AQS 如何处理线程中断?

  • 不可中断模式(默认):*

    线程调用 acquire() 时被中断,会继续等待直到获取资源,再补上中断标记。
    
  • 可中断模式:*

    线程调用 acquireInterruptibly() 时被中断,直接抛出 InterruptedException。
    

5. 共享模式下的唤醒传播是什么?

共享模式(如 Semaphore)释放资源时,会唤醒队列中的第一个等待节点,
并由该节点继续唤醒后续共享节点,形成“连锁反应”,直到无可用资源或队列为空。

优势:减少唤醒次数,提升并发效率。

6. AQS 中的同步状态(state)有什么作用?

答:同步状态(state)用于表示同步器的状态,不同的同步器对 state 的含义和操作方式不同。
例如在 ReentrantLock 中表示锁的重入次数,在 Semaphore 中表示可用的许可证数量。

7. AQS 队列的作用是什么?

答:AQS 队列用于存储等待获取同步状态的线程节点,当线程获取同步状态失败时,会被加入到队列中等待唤醒。

8. 说说 AQS 中 acquire 和 acquireInterruptibly 方法的区别?

答:acquire 方法是独占式获取同步状态,如果获取失败则将当前线程加入等待队列并阻塞,且不响应中断;
acquireInterruptibly也是独占式获取同步状态,但允许线程被中断,
当线程被中断时会抛出 InterruptedException。

9. 如何基于 AQS 实现一个简单的自定义同步器?

答:首先继承 AQS 类,然后实现 tryAcquire、tryRelease(独占模式)或                 
tryAcquireShared、tryReleaseShared(共享模式)等方法来定义同步状态的获取和释放逻辑。
例如,要实现一个简单的独占锁,可以如下实现:
import java.util.concurrent.locks.AbstractQueuedSynchronizer;

class MyLock {
    private static class Sync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire(int arg) {
            // 尝试获取锁,只有当 state 为 0 时才能获取成功
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            // 释放锁,将 state 设为 0
            if (getState() == 0) {
                throw new IllegalMonitorStateException();
            }
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }
    }

    private final Sync sync = new Sync();

    public void lock() {
        sync.acquire(1);
    }

    public void unlock() {
        sync.release(1);
    }
}

高旭
40 声望3 粉丝