深入分析Java中的AQS:从应用到原理的思维链条

深入分析Java中的AQS:从应用到原理的思维链条

AQS(AbstractQueuedSynchronizer)是Java并发编程中的核心框架,它为众多同步工具提供了基础支持。让我通过链路追踪的方式,从应用场景逐步深入探究其工作原理。

1. 从应用场景出发

在日常开发中,我们常用的锁和同步工具包括:

  • ReentrantLock:可重入锁,替代synchronized
  • CountDownLatch:倒计时门闩,等待一组线程完成
  • Semaphore:信号量,控制同时访问的线程数
  • ReentrantReadWriteLock:读写锁,读共享写互斥
  • CyclicBarrier:循环栅栏,等待一组线程达到某个点

这些工具虽然功能各异,但它们的内部实现都基于AQS。

2. AQS的核心设计思想

AQS提供了一个框架,通过模板方法模式实现了同步器的骨架,具体同步工具只需实现少量方法即可。

核心设计要点:

  • 状态管理 :维护一个volatile int state表示同步状态
  • 队列管理:通过一个FIFO队列管理等待线程
  • 阻塞原语 :使用LockSupport.park()/unpark()实现线程阻塞/唤醒

3. AQS工作机制链路追踪

以ReentrantLock为例,让我们追踪其获取锁和释放锁的完整流程:

3.1 获取锁流程

scss 复制代码
lock() → acquire(1) → tryAcquire(1) → 
  成功 → 获取锁成功并返回
  失败 → addWaiter(Node.EXCLUSIVE) → enq(node) → acquireQueued(node, 1) → 
    parkAndCheckInterrupt() → LockSupport.park(this)
  1. 调用lock()方法
  2. lock()内部调用AQS的acquire(1)
  3. acquire(1)先调用tryAcquire(1)尝试获取锁
    • 如果成功,直接返回
    • 如果失败,继续下一步
  4. 调用addWaiter(Node.EXCLUSIVE)创建独占模式的节点
  5. 通过enq(node)将节点加入队列
  6. 调用acquireQueued(node, 1)使线程阻塞等待
  7. 阻塞是通过parkAndCheckInterrupt()实现的
  8. 内部调用LockSupport.park(this)让线程暂停

3.2 释放锁流程

scss 复制代码
unlock() → release(1) → tryRelease(1) → 
  成功 → unparkSuccessor(h) → LockSupport.unpark(s.thread)
  1. 调用unlock()方法
  2. unlock()内部调用AQS的release(1)
  3. release(1)调用tryRelease(1)尝试释放锁
  4. 如果释放成功,调用unparkSuccessor(h)唤醒后继节点
  5. 唤醒是通过LockSupport.unpark(s.thread)实现的

4. AQS核心源码剖析

4.1 状态管理

java 复制代码
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);
}
  • state是AQS的核心字段,不同工具对其有不同解释
  • 在ReentrantLock中,表示锁被同一线程重入的次数
  • 在Semaphore中,表示剩余许可数量
  • 在CountDownLatch中,表示剩余计数

4.2 队列管理

java 复制代码
private transient volatile Node head; // 队列头节点
private transient volatile Node tail; // 队列尾节点

// Node节点定义
static final class Node {
    volatile int waitStatus; // 等待状态
    volatile Node prev;      // 前驱节点
    volatile Node next;      // 后继节点
    volatile Thread thread;  // 关联的线程
    Node nextWaiter;         // 下一个等待者
    
    // 等待状态常量
    static final int CANCELLED =  1; // 取消状态
    static final int SIGNAL    = -1; // 信号状态(后继节点需要唤醒)
    static final int CONDITION = -2; // 条件等待
    static final int PROPAGATE = -3; // 共享模式传播
}
  • AQS通过一个双向链表实现的FIFO队列来管理等待线程
  • 每个节点对应一个线程,以及该线程的等待状态
  • waitStatus的不同值表示节点的不同状态

4.3 阻塞与唤醒

java 复制代码
// 线程阻塞(停车)
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

// 唤醒后继节点
private void unparkSuccessor(Node node) {
    // 如果状态为负,尝试将其置为0
    int ws = node.waitStatus;
    if (ws < 0) compareAndSetWaitStatus(node, ws, 0);
    
    // 找到需要唤醒的后继节点
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        // 从尾部向前查找最前面的一个状态<=0的节点
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0) s = t;
    }
    // 唤醒后继节点
    if (s != null) LockSupport.unpark(s.thread);
}
  • AQS使用LockSupport的park/unpark机制实现线程的阻塞和唤醒
  • 这比wait/notify更灵活,可以精确控制要唤醒的线程

5. AQS的两种模式

AQS支持两种同步模式,不同的工具使用不同模式:

5.1 独占模式(Exclusive)

  • 同一时刻只有一个线程能获取资源
  • 实现类需要覆盖的方法:
    • tryAcquire(int)
    • tryRelease(int)
  • 典型实现:ReentrantLock

5.2 共享模式(Shared)

  • 同一时刻可以有多个线程获取资源
  • 实现类需要覆盖的方法:
    • tryAcquireShared(int)
    • tryReleaseShared(int)
  • 典型实现:CountDownLatch、Semaphore、ReadLock

6. 记忆与理解的思维链条

为了更好地记忆和理解AQS,可以建立以下思维链条:

  1. 应用层:各种同步工具(ReentrantLock, CountDownLatch等)
  2. 框架层:AQS框架(模板方法设计模式)
  3. 状态控制:volatile int state(CAS操作)
  4. 队列管理:双向FIFO队列(Node节点链表)
  5. 线程控制:park/unpark机制(LockSupport工具类)
  6. 模式选择:独占模式vs共享模式

7. AQS的精华总结

  1. 设计精妙:通过简单的状态变量和队列,实现了复杂的同步语义
  2. 可扩展性:模板方法设计模式,允许不同工具定制不同行为
  3. 高性能:使用CAS操作避免锁的开销
  4. 易用性:屏蔽了并发编程的复杂性,提供简单统一的接口

通过这种链路式的分析,我们可以看到AQS是如何从一个简单的抽象框架,支撑起Java中各种丰富多样的同步工具。理解AQS,就掌握了Java并发编程的核心基石。

相关推荐
不再幻想,脚踏实地2 小时前
Spring AOP从0到1
java·后端·spring
编程乐学(Arfan开发工程师)2 小时前
07、基础入门-SpringBoot-自动配置特性
java·spring boot·后端
会敲键盘的猕猴桃很大胆2 小时前
Day11-苍穹外卖(数据统计篇)
java·spring boot·后端·spring·信息可视化
极客智谷2 小时前
Spring Cloud动态配置刷新:@RefreshScope与@Component的协同机制解析
后端·spring·spring cloud
Lizhihao_2 小时前
Spring MVC 接口的访问方法如何设置
java·后端·spring·mvc
Code哈哈笑7 小时前
【图书管理系统】用户注册系统实现详解
数据库·spring boot·后端·mybatis
用手手打人7 小时前
SpringBoot(一)--- Maven基础
spring boot·后端·maven
Code哈哈笑8 小时前
【基于Spring Boot 的图书购买系统】深度讲解 用户注册的前后端交互,Mapper操作MySQL数据库进行用户持久化
数据库·spring boot·后端·mysql·mybatis·交互
Javatutouhouduan9 小时前
线上问题排查:JVM OOM问题如何排查和解决
java·jvm·数据库·后端·程序员·架构师·oom
多多*9 小时前
Spring之Bean的初始化 Bean的生命周期 全站式解析
java·开发语言·前端·数据库·后端·spring·servlet