🔑 AQS抽象队列同步器:Java并发编程的"万能钥匙"

一、AQS是个啥?为啥这么重要?🤔

AQS = AbstractQueuedSynchronizer(抽象队列同步器)

想象一下,你要去苹果店🍎买最新款iPhone。店门口排着长队,只有一个门,一次只能进一个人。这就是AQS的工作原理!

  • = 共享资源(锁)
  • 排队的人 = 等待线程
  • 队列 = CLH队列
  • 保安 = AQS控制器

🎯 为什么AQS这么牛?

Java并发包(J.U.C)里的大量工具都是基于AQS实现的:

markdown 复制代码
AQS(框架层)
    ├── ReentrantLock(独占锁)🔒
    ├── ReentrantReadWriteLock(读写锁)📖
    ├── Semaphore(信号量)🚦
    ├── CountDownLatch(倒计时门栓)⏱️
    ├── CyclicBarrier(循环栅栏)🚧
    └── ThreadPoolExecutor.Worker(线程池)🏊

一句话总结:

Doug Lea大神创造了AQS这个模板框架,让你只需要实现几个方法,就能自定义各种同步器!就像给你一个"万能钥匙模具"🔑,你只需要稍微调整就能开各种锁!

二、AQS核心数据结构 📊

1️⃣ 状态变量 state

java 复制代码
// AQS的核心:一个int类型的状态变量
private volatile int state;

// 为啥用volatile?
// ✅ 保证可见性:一个线程修改,其他线程立即看到
// ✅ 禁止指令重排序

state的含义取决于具体实现:

同步器 state含义 举例
ReentrantLock 0=未锁定,1=已锁定,>1=重入次数 state=3表示锁被重入了3次
Semaphore 可用许可数量 state=5表示还有5个许可
CountDownLatch 倒计时数量 state=3表示还需要3次countDown
ReentrantReadWriteLock 高16位=读锁数量,低16位=写锁数量 state=65537表示1个读锁

2️⃣ CLH队列(核心!)

CLH = Craig, Landin, and Hagersten三位大神的名字首字母

scss 复制代码
        AQS内部的等待队列
        
        Head                    Tail
         ↓                       ↓
       ┌────┐    ┌────┐    ┌────┐    ┌────┐
   →   │Node│ ←→ │Node│ ←→ │Node│ ←→ │Node│
       └────┘    └────┘    └────┘    └────┘
         │         │         │         │
       Thread1   Thread2   Thread3   Thread4
       (持锁)    (等待)    (等待)    (等待)

双向链表结构:

java 复制代码
static final class Node {
    // 前驱节点
    volatile Node prev;
    
    // 后继节点  
    volatile Node next;
    
    // 当前线程
    volatile Thread thread;
    
    // 等待状态
    volatile int waitStatus;
    
    // 下一个等待条件队列的节点(Condition用)
    Node nextWaiter;
}

3️⃣ Node的等待状态 waitStatus

java 复制代码
// 等待状态的几种取值
static final int CANCELLED =  1;  // 取消状态(超时/中断)
static final int SIGNAL    = -1;  // 后继节点需要被唤醒
static final int CONDITION = -2;  // 在条件队列中等待
static final int PROPAGATE = -3;  // 共享模式下的传播
static final int INITIAL   =  0;  // 初始状态

生活比喻:

scss 复制代码
🏪 苹果店门口排队买iPhone

SIGNAL(-1): "嘿,后面的兄弟,我买完了会叫你!"  
CANCELLED(1): "算了,不买了,我走了!"  
CONDITION(-2): "等通知说有货了我再来!"  
PROPAGATE(-3): "有货了!大家一起进!"  
INITIAL(0): 刚来,还没想好  

三、AQS两种模式 🎭

独占模式(Exclusive)🔒

一次只有一个线程能获取资源,就像单人厕所🚻,只能一个人用!

java 复制代码
// ReentrantLock就是独占模式
ReentrantLock lock = new ReentrantLock();

lock.lock();    // 占厕所
try {
    // 上厕所中...💩
} finally {
    lock.unlock();  // 出来,下一位!
}

共享模式(Shared)👥

多个线程可以同时获取资源,就像电影院🎬,多个座位可以同时坐人!

java 复制代码
// Semaphore就是共享模式(5个座位)
Semaphore semaphore = new Semaphore(5);

semaphore.acquire();  // 占一个座位
try {
    // 看电影中...🍿
} finally {
    semaphore.release();  // 释放座位
}

四、AQS工作流程(独占模式)🎬

场景:三个线程抢一个锁

java 复制代码
// 三个线程同时执行
Thread-1: lock.lock();
Thread-2: lock.lock();
Thread-3: lock.lock();

📽️ 电影开始

第1帧:Thread-1首先到达

ini 复制代码
1. 调用 tryAcquire(1) 尝试获取锁
2. state从0变成1,成功!✅
3. 设置 exclusiveOwnerThread = Thread-1
4. Thread-1愉快地执行临界区代码 🎉

当前状态:
state = 1
exclusiveOwnerThread = Thread-1
队列:空

第2帧:Thread-2到达,发现锁被占

scss 复制代码
1. 调用 tryAcquire(1)
2. 发现state=1,获取失败 ❌
3. 调用 addWaiter() 加入等待队列
4. 创建Node节点,放入队尾
5. 调用 acquireQueued() 开始等待
6. 自旋检查前驱节点是否是head
7. 发现不是,调用 LockSupport.park() 挂起 😴

当前状态:
        Head           Tail
         ↓              ↓
       ┌────┐        ┌────┐
       │Node│  ←→    │Node│
       └────┘        └────┘
         │             │
      (空节点)      Thread-2
                    (SIGNAL)

第3帧:Thread-3到达

scss 复制代码
同样的流程,加入队尾

        Head                      Tail
         ↓                         ↓
       ┌────┐    ┌────┐        ┌────┐
       │Node│ ←→ │Node│  ←→    │Node│
       └────┘    └────┘        └────┘
         │         │             │
      (空节点)  Thread-2      Thread-3
                (SIGNAL)      (SIGNAL)

第4帧:Thread-1释放锁

scss 复制代码
1. Thread-1调用 unlock()
2. 调用 tryRelease(1)
3. state从1变成0
4. exclusiveOwnerThread = null
5. 调用 unparkSuccessor() 唤醒后继节点
6. LockSupport.unpark(Thread-2) 唤醒Thread-2 ⏰

当前状态:
state = 0
exclusiveOwnerThread = null

第5帧:Thread-2被唤醒

markdown 复制代码
1. Thread-2从park()中醒来 😊
2. 再次调用 tryAcquire(1)
3. 成功获取锁!state=1
4. 设置自己为head节点
5. 移除旧head节点
6. Thread-2开始执行临界区代码

当前状态:
        Head           Tail
         ↓              ↓
       ┌────┐        ┌────┐
       │Node│  ←→    │Node│
       └────┘        └────┘
         │             │
      Thread-2      Thread-3
      (持锁)        (等待)

五、核心源码解析 💻

1️⃣ 获取锁流程(acquire)

java 复制代码
public final void acquire(int arg) {
    // 三个步骤:
    // 1. 尝试获取锁
    if (!tryAcquire(arg) &&
        // 2. 获取失败,加入队列
        acquireQueued(
            // 3. 创建节点并加入队尾
            addWaiter(Node.EXCLUSIVE), arg)) {
        // 如果被中断,设置中断标志
        selfInterrupt();
    }
}

完整流程图:

scss 复制代码
开始
  │
  ├─→ tryAcquire(1) ─→ 成功? ─→ Yes ─→ 结束(拿到锁)✅
  │                      │
  │                      No
  │                      ↓
  ├─→ addWaiter() ─→ 创建Node ─→ 加入队尾
  │                      ↓
  └─→ acquireQueued() ─→ 自旋等待
         │                  │
         │                  ├─→ 前驱是head? ─→ Yes ─→ tryAcquire ─→ 成功? ─→ 结束✅
         │                  │                                        │
         │                  │                                        No
         │                  No                                       │
         │                  │                                        ↓
         │                  ↓                                     park() 😴
         │               park() 😴                                   │
         │                  │                                        │
         │              被唤醒 ⏰ ←──────────────────────────────────┘
         │                  │
         └─────────────────→┘(循环)

2️⃣ 释放锁流程(release)

java 复制代码
public final boolean release(int arg) {
    // 1. 尝试释放锁
    if (tryRelease(arg)) {
        Node h = head;
        // 2. 如果head不为空且状态不是初始态
        if (h != null && h.waitStatus != 0)
            // 3. 唤醒后继节点
            unparkSuccessor(h);
        return true;
    }
    return false;
}

// 唤醒后继节点
private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        // CAS设置为0
        compareAndSetWaitStatus(node, ws, 0);
    
    Node s = node.next;
    // 找到第一个需要唤醒的节点
    if (s == null || s.waitStatus > 0) {
        s = null;
        // 从尾部往前找
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);  // 唤醒!⏰
}

六、Condition条件队列 🎪

除了CLH同步队列,AQS还支持条件队列(配合Lock使用):

java 复制代码
ReentrantLock lock = new ReentrantLock();
Condition notFull = lock.newCondition();   // 队列未满条件
Condition notEmpty = lock.newCondition();  // 队列非空条件

// 生产者
lock.lock();
try {
    while (queue.isFull()) {
        notFull.await();  // 等待"未满"条件
    }
    queue.add(item);
    notEmpty.signal();  // 通知"非空"条件
} finally {
    lock.unlock();
}

// 消费者  
lock.lock();
try {
    while (queue.isEmpty()) {
        notEmpty.await();  // 等待"非空"条件
    }
    queue.take();
    notFull.signal();  // 通知"未满"条件
} finally {
    lock.unlock();
}

Condition原理:

yaml 复制代码
同步队列(CLH队列)
┌────┐    ┌────┐    ┌────┐
│Node│ ←→ │Node│ ←→ │Node│
└────┘    └────┘    └────┘

条件队列(单向链表)
notFull: ┌────┐ → ┌────┐ → null
         │Node│   │Node│
         └────┘   └────┘
         
notEmpty: ┌────┐ → null
          │Node│
          └────┘

七、手写一个简单的AQS应用 ✍️

java 复制代码
// 自定义一个独占锁(类似ReentrantLock)
public class MyLock {
    
    // 继承AQS
    private static class Sync extends AbstractQueuedSynchronizer {
        
        // 尝试获取锁
        @Override
        protected boolean tryAcquire(int arg) {
            // CAS设置state从0到1
            if (compareAndSetState(0, 1)) {
                // 设置当前线程为独占线程
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
        
        // 尝试释放锁
        @Override
        protected boolean tryRelease(int arg) {
            if (getState() == 0) {
                throw new IllegalMonitorStateException();
            }
            // 清空独占线程
            setExclusiveOwnerThread(null);
            // 释放state
            setState(0);
            return true;
        }
        
        // 是否持有锁
        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }
    }
    
    private final Sync sync = new Sync();
    
    // 加锁
    public void lock() {
        sync.acquire(1);
    }
    
    // 解锁
    public void unlock() {
        sync.release(1);
    }
}

// 使用
MyLock lock = new MyLock();
lock.lock();
try {
    // 临界区代码
    System.out.println("拿到锁了!");
} finally {
    lock.unlock();
}

八、面试应答模板 🎤

面试官:说说AQS的核心原理和数据结构?

你的回答:

AQS是AbstractQueuedSynchronizer的缩写,是Java并发包的基础框架,ReentrantLock、Semaphore等都基于它实现。

核心数据结构有三个:

1️⃣ state变量(volatile int):表示同步状态

  • 在ReentrantLock中表示锁的重入次数
  • 在Semaphore中表示可用许可数
  • 通过CAS操作保证原子性

2️⃣ CLH队列(双向链表):存储等待线程

  • 每个节点包含:thread、waitStatus、prev、next
  • waitStatus有SIGNAL、CANCELLED、CONDITION等状态
  • head节点是空节点或持锁线程,tail是队尾

3️⃣ 独占线程(exclusiveOwnerThread):记录当前持锁线程

工作原理:

  • 线程尝试获取资源(tryAcquire),成功则执行
  • 失败则创建Node节点加入CLH队列尾部
  • 在队列中自旋检查前驱是否是head,是则尝试获取
  • 否则调用LockSupport.park()挂起等待
  • 持锁线程释放资源时,唤醒后继节点
  • 被唤醒的线程继续尝试获取资源

支持两种模式:

  • 独占模式(Exclusive):如ReentrantLock
  • 共享模式(Shared):如Semaphore、CountDownLatch

九、总结图示 🎨

java 复制代码
🏗️ AQS架构全景图

            ┌─────────────────────────────┐
            │   AbstractQueuedSynchronizer │
            │                              │
            │  ┌────────────────────────┐  │
            │  │  volatile int state    │  │ ← 核心状态
            │  └────────────────────────┘  │
            │                              │
            │  ┌────────────────────────┐  │
            │  │   CLH Queue (双向链表)  │  │
            │  │                        │  │
            │  │  Head → Node ↔ Node   │  │ ← 等待队列
            │  │                ↓       │  │
            │  │              Tail      │  │
            │  └────────────────────────┘  │
            └─────────────────────────────┘
                        ↑
                        │ 继承
        ┌───────────────┴───────────────┐
        │                               │
   ReentrantLock.Sync            Semaphore.Sync
   (独占模式)                     (共享模式)

记忆口诀:

AQS是个大管家,

state状态它来管,

CLH队列排排站,

独占共享两模式,

CAS操作保安全,

park和unpark来睡眠,

并发工具都靠它!🎵


核心要点:

  • ✅ AQS是J.U.C的基石
  • ✅ state + CLH队列 + CAS操作
  • ✅ 独占模式 vs 共享模式
  • ✅ park/unpark控制线程挂起唤醒
  • ✅ 模板方法设计模式

Doug Lea大神说:

"给我一个AQS,我能撬动整个并发世界!"🌍

相关推荐
yren4 小时前
Mysql 多版本并发控制 MVCC
后端
回家路上绕了弯4 小时前
外卖员重复抢单?从技术到运营的全链路解决方案
分布式·后端
考虑考虑4 小时前
解决idea导入项目出现不了maven
java·后端·maven
数据飞轮4 小时前
不用联网、不花一分钱,这款开源“心灵守护者”10分钟帮你建起个人情绪疗愈站
后端
Amos_Web4 小时前
Rust实战课程--网络资源监控器(初版)
前端·后端·rust
程序猿小蒜4 小时前
基于springboot的基于智能推荐的卫生健康系统开发与设计
java·javascript·spring boot·后端·spring
渣哥4 小时前
IOC 容器的进化:ApplicationContext 在 Spring 中的核心地位
javascript·后端·面试
Gu_yyqx4 小时前
Spring 框架
java·后端·spring
demo007x5 小时前
如何让 Podman 使用国内镜像源,这是我见过最牛的方法
后端·程序员