AQS(AbstractQueuedSynchronizer)是Java并发包的核心同步框架,通过状态变量(state)CLH变体等待队列实现线程安全的同步机制,采用模板方法模式让子类只需实现tryAcquire/tryRelease等钩子方法即可构建自定义同步器。其核心价值在于:统一管理线程阻塞/唤醒流程(独占模式如ReentrantLock、共享模式如Semaphore/CountDownLatch),通过volatile state保证可见性,结合CAS原子操作实现高效并发控制。典型应用场景包括高并发资源池管理(库存扣减)、多线程任务协调(CountDownLatch)、读写分离(ReentrantReadWriteLock),显著简化了同步器的开发复杂度,成为Java并发工具链的基石。

一、AQS概述

AbstractQueuedSynchronizer(AQS)是Java并发包(java.util.concurrent.locks)的核心基础框架,为构建锁和同步器提供了可重用的基础设施。AQS通过模板方法模式让开发者能够基于状态和队列轻松构建各种同步器,如ReentrantLock、Semaphore、CountDownLatch等常见并发工具。

AQS的核心设计思想:如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效工作线程,并将共享资源设置为锁定状态;如果共享资源被占用,则需要一套线程阻塞等待以及被唤醒时锁分配的机制。这套机制通过CLH队列锁实现,将暂时获取不到锁的线程加入到队列中。

与传统的synchronized关键字相比,AQS具有显著优势:它由Java语言实现(而非C++),提供了更灵活的API,并且在锁竞争激烈的情况下提供了多种解决方案,性能表现更优。

二、AQS核心原理与架构设计

2.1 核心组件:状态变量与同步队列

2.1.1 同步状态(state)

  • 本质:一个volatile int类型的变量,表示共享资源的状态
  • 语义:具体语义由子类定义
    • ReentrantLock:锁的重入次数
    • Semaphore:可用许可证数量
    • CountDownLatch:未完成的事件数
    • ReentrantReadWriteLock:高16位为读锁计数,低16位为写锁重入计数

AQS提供的原子操作方法

protected final int getState() { return state; }
protected final void setState(int newState) { state = newState; }
protected final boolean compareAndSetState(int expect, int update) {
    // CAS操作,保证原子性
}

2.1.2 等待队列(CLH变体)

  • 本质:FIFO双向链表,用于存储等待获取锁的线程
  • 节点(Node):包含线程引用、等待状态以及前后指针
  • 节点状态
    • CANCELLED(1):节点因超时或中断被取消,不再参与同步
    • SIGNAL(-1):后继节点需要被唤醒
    • CONDITION(-2):节点在条件队列中等待
    • PROPAGATE(-3):共享模式下状态变更需要传播
    • 0:初始状态

2.2 AQS工作原理

AQS通过模板方法模式,将同步器的通用逻辑封装在基类中,子类只需实现特定的钩子方法即可构建完整的同步组件。

核心工作流程

  1. 线程尝试获取同步器(acquire方法)
  2. 如果获取成功,继续执行
  3. 如果获取失败,将线程包装成Node加入等待队列并被阻塞
  4. 当资源释放时,唤醒等待队列中的下一个线程
  5. 被唤醒的线程尝试重新获取资源

三、AQS关键方法

3.1 获取资源

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
  • tryAcquire:抽象方法,由子类实现,尝试获取资源
  • addWaiter:将当前线程加入等待队列
  • acquireQueued:自旋获取锁
  • selfInterrupt:如果获取失败,中断当前线程

详细流程

  1. 调用tryAcquire尝试获取资源
  2. 获取失败后,调用addWaiter将当前线程加入等待队列
  3. 调用acquireQueued自旋获取锁:
    • 检查前驱节点状态
    • 如果前驱节点是SIGNAL状态,挂起当前线程
    • 被唤醒后继续尝试获取锁
  4. 获取成功后,返回

3.2 释放资源

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:抽象方法,由子类实现,尝试释放资源
  • unparkSuccessor:唤醒等待队列中的下一个线程

四、AQS在并发工具中的应用

4.1 ReentrantLock(独占锁)

基于AQS独占模式实现的可重入互斥锁:

  • state语义:锁的重入次数
  • 公平性
    • 公平锁:按FIFO顺序获取锁
    • 非公平锁:允许插队,性能更高

代码示例

ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
    // 临界区操作
} finally {
    lock.unlock();
}

实现原理

  • tryAcquire:检查state是否为0或当前线程是否已经持有锁
  • tryRelease:减少state,当state为0时释放锁

4.2 Semaphore(信号量)

基于AQS共享模式实现的计数信号量:

  • state语义:可用许可证数量
  • 应用场景:资源池管理、限流控制

代码示例

Semaphore semaphore = new Semaphore(10); // 最大10个许可
semaphore.acquire(); // 获取许可
try {
    // 访问受限资源
} finally {
    semaphore.release(); // 释放许可
}

实现原理

  • tryAcquireShared:检查可用许可证数量
  • tryReleaseShared:增加可用许可证数量

4.3 CountDownLatch(倒计时门闩)

基于AQS共享模式实现的一次性屏障:

  • state语义:未完成的事件数
  • 应用场景:多线程任务协调

代码示例

CountDownLatch latch = new CountDownLatch(5); // 5个事件
// 工作线程
latch.countDown(); // 事件完成
// 主线程
latch.await(); // 等待所有事件完成

实现原理

  • tryAcquireShared:检查state是否为0
  • tryReleaseShared:减少state,当state为0时唤醒等待线程

4.4 ReentrantReadWriteLock(读写锁)

组合独占(写)和共享(读)模式的读写锁:

  • state语义
    • 高16位:读锁计数
    • 低16位:写锁重入计数
  • 应用场景:读多写少的并发访问

代码示例

ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
rwLock.readLock().lock(); // 获取读锁
try {
    // 读操作
} finally {
    rwLock.readLock().unlock();
}

rwLock.writeLock().lock(); // 获取写锁
try {
    // 写操作
} finally {
    rwLock.writeLock().unlock();
}

五、AQS关键流程分析

5.1 独占锁获取流程

  1. 调用acquire方法
  2. 调用tryAcquire尝试获取锁
  3. 获取失败后,调用addWaiter将当前线程加入等待队列
  4. 调用acquireQueued自旋获取锁:
    • 检查前驱节点状态
    • 如果前驱节点是SIGNAL状态,挂起当前线程(parkAndCheckInterrupt)
    • 被唤醒后继续尝试获取锁
  5. 获取成功后,返回

5.2 独占锁释放流程

  1. 调用release方法
  2. 调用tryRelease尝试释放锁
  3. 释放成功后,调用unparkSuccessor唤醒等待队列中的下一个线程
  4. 被唤醒的线程继续尝试获取锁

六、公平锁与非公平锁

6.1 非公平锁(默认)

  • 特点:允许插队,性能更高
  • 实现:在尝试获取锁时,不检查等待队列,直接尝试获取

ReentrantLock默认构造

public ReentrantLock() {
    sync = new NonfairSync();
}

6.2 公平锁

  • 特点:按FIFO顺序获取锁,保证公平性
  • 实现:在尝试获取锁时,检查等待队列,确保先来后到

创建公平锁

ReentrantLock fairLock = new ReentrantLock(true);

七、AQS项目实战

7.1 电商库存扣减(ReentrantLock)

高并发场景下保证库存扣减的原子性:

public class Inventory {
    private int stock;
    private final ReentrantLock lock = new ReentrantLock();

    public boolean deduct() {
        lock.lock();
        try {
            if (stock > 0) {
                stock--;
                return true;
            }
            return false;
        } finally {
            lock.unlock();
        }
    }
}

7.2 自定义同步器:简易版ReentrantLock

import java.util.concurrent.locks.AbstractQueuedSynchronizer;

public class MyReentrantLock {
    private final Sync sync = new Sync();

    private static class Sync extends AbstractQueuedSynchronizer {
        @Override
        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()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

        @Override
        protected boolean isHeldExclusively() {
            return getState() != 0 && getExclusiveOwnerThread() == Thread.currentThread();
        }
    }

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

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

    public boolean isLocked() {
        return sync.isHeldExclusively();
    }
}

八、AQS的适用场景

8.1 实现独占锁

  • 示例:ReentrantLock
  • 场景:需要互斥访问的资源,如数据库连接池、共享缓存
  • 特点:同一时刻只允许一个线程访问资源

8.2 实现共享锁

  • 示例:Semaphore、CountDownLatch
  • 场景:资源池管理(如线程池、数据库连接池)、限流控制、多线程任务协调
  • 特点:允许多个线程同时访问资源

8.3 实现自定义同步器

  • 示例:自定义的读写锁、资源池管理器
  • 场景:特定业务场景下的同步需求,如库存管理、订单处理
  • 特点:根据业务需求定制同步逻辑

8.4 高并发场景下的资源控制

  • 示例:电商库存扣减、秒杀系统
  • 场景:需要保证原子性操作的高并发场景
  • 特点:保证操作的原子性和一致性

8.5 多线程任务协调

  • 示例:CountDownLatch、CyclicBarrier
  • 场景:等待多个线程完成任务后再继续执行
  • 特点:实现线程间的同步与协调

九、AQS的优缺点

9.1 优点

  1. 统一框架:提供统一的同步框架,简化了同步器的实现
  2. 灵活性:支持独占模式和共享模式,满足不同同步需求
  3. 性能:通过CLH队列和CAS操作,保证了高并发性能
  4. 可扩展性:易于扩展和定制,满足特定业务需求
  5. 与Java并发包无缝集成:与Java并发包的其他组件无缝集成

9.2 缺点

  1. 学习曲线:对不熟悉并发编程的开发者有一定学习难度
  2. 实现复杂度:实现自定义同步器需要正确处理状态转换和线程调度
  3. 非公平模式:可能导致线程饥饿问题(虽然性能更好)
  4. 调试难度:并发问题难以复现和调试

十、总结

AQS是Java并发编程中一个非常重要的基础框架,它为多种同步结构提供了强大而灵活的底层支持。通过精细的多线程管理和内部状态控制,AQS使得开发人员能够相对容易地构建出符合特定需求的同步器。

关键点总结

  • AQS的核心是状态变量(state)等待队列
  • 采用模板方法模式,子类只需实现特定的钩子方法
  • 支持独占模式(如ReentrantLock)和共享模式(如Semaphore)
  • 通过CLH队列实现线程的公平/非公平调度
  • 为Java并发包中的各种同步工具提供了底层支持

理解AQS的工作原理对于深入掌握Java并发编程至关重要,它不仅是ReentrantLock、Semaphore等并发工具的基础,也是实现自定义同步器的基石。通过AQS,开发者可以构建出高效、可靠的并发应用程序,满足各种复杂的同步需求。