深入分析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并发编程的核心基石。

相关推荐
ChinaRainbowSea7 分钟前
3. RabbitMQ 的(Hello World) 和 RabbitMQ 的(Work Queues)工作队列
java·分布式·后端·rabbitmq·ruby·java-rabbitmq
dleei22 分钟前
MySql安装及SQL语句
数据库·后端·mysql
CryptoPP37 分钟前
springboot 对接马来西亚数据源API等多个国家的数据源
spring boot·后端·python·金融·区块链
Source.Liu1 小时前
【学Rust写CAD】27 双线性插值函数(bilinear_interpolation.rs)
后端·rust·cad
yinhezhanshen1 小时前
理解rust里面的copy和clone
开发语言·后端·rust
uhakadotcom1 小时前
Helm 简介与实践指南
后端·面试·github
栗筝i1 小时前
Spring 核心技术解析【纯干货版】- XIX:Spring 日志模块 Spring-Jcl 模块精讲
java·后端·spring
企鹅不耐热.2 小时前
Scala基础知识6
开发语言·后端·scala
程序员一诺2 小时前
【Django开发】前后端分离django美多商城项目第15篇:商品搜索,1. Haystack介绍和安装配置【附代码文档】
后端·python·django·框架
冷琅辞2 小时前
Go语言的嵌入式网络
开发语言·后端·golang