轻松的通过ReentrantLock掌握AQS的核心原理

摘要:从一次"线程池卡死导致应用假死"的线上故障出发,通过ReentrantLock的使用案例反推AQS的核心原理。深度剖析AQS的state变量、CLH队列、独占模式与共享模式,配合手写100行代码实现简易版ReentrantLock,揭秘为什么ReentrantLock比synchronized更灵活、公平锁与非公平锁的性能差异、以及Condition条件队列的实现原理。图解入队出队流程,给出并发工具类的最佳实践。


💥 翻车现场

周四下午3点,哈吉米收到告警。

告警:
🚨 应用服务器CPU 0%(假死)
🚨 所有接口超时
🚨 日志没有新输出
🚨 用户反馈:一直加载中

哈吉米:"卧槽,应用假死了?"

紧急重启应用后,查看线程dump:

"pool-1-thread-1" #12 prio=5 waiting on condition
   java.lang.Thread.State: WAITING (parking)
   at sun.misc.Unsafe.park(Native Method)
   at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
   at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt
   at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued
   at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire
   at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock

"pool-1-thread-2" #13 prio=5 waiting on condition
   java.lang.Thread.State: WAITING (parking)
   ... (同样在等待锁)

"pool-1-thread-3" #14 prio=5 waiting on condition
   ... (100个线程都在等待同一把锁)

哈吉米:"所有线程都在等待 AbstractQueuedSynchronizer?这是啥?"

查看代码:

private final ReentrantLock lock = new ReentrantLock();

public void processOrder(Order order) {
    lock.lock();
    try {
        // 处理订单
        doProcess(order);
        
        // 调用外部接口(可能很慢)
        externalService.notify(order);  // 这个接口超时了,一直不返回
        
    } finally {
        lock.unlock();  // 永远执行不到这里
    }
}

哈吉米:"卧槽,外部接口超时,锁一直没释放,其他线程全部等待!"

晚上,南北绿豆和阿西噶阿西来了。

南北绿豆:"你看到的 AbstractQueuedSynchronizer 就是大名鼎鼎的AQS!"
哈吉米:"AQS是啥?"
阿西噶阿西:"AQS是Java并发工具类的基石,ReentrantLock、CountDownLatch、Semaphore底层都用它实现。"
南北绿豆:"来,我通过ReentrantLock给你讲讲AQS的原理。"


🤔 什么是AQS?

AQS的全称

AbstractQueuedSynchronizer(抽象队列同步器)

南北绿豆:"AQS是JUC(java.util.concurrent)包的核心框架。"


AQS的核心组件

public abstract class AbstractQueuedSynchronizer {
    
    // 1. state变量(同步状态)
    private volatile int state;
    
    // 2. CLH队列(等待队列)
    private transient volatile Node head;  // 队列头
    private transient volatile Node tail;  // 队列尾
    
    // 3. Node节点(等待线程的封装)
    static final class Node {
        volatile Thread thread;     // 等待的线程
        volatile Node prev;         // 前驱节点
        volatile Node next;         // 后继节点
        volatile int waitStatus;    // 等待状态
    }
}

三大核心

组件作用
state变量表示同步状态(0=未锁定,1=已锁定)
CLH队列存储等待线程的队列(双向链表)
Node节点封装等待的线程

🎯 通过ReentrantLock理解AQS

ReentrantLock的使用

private final ReentrantLock lock = new ReentrantLock();

public void processOrder(Order order) {
    lock.lock();  // 获取锁
    try {
        // 处理订单(临界区)
        doProcess(order);
    } finally {
        lock.unlock();  // 释放锁
    }
}

ReentrantLock的底层实现

public class ReentrantLock implements Lock {
    
    // 内部使用AQS实现
    private final Sync sync;
    
    // Sync继承自AQS
    abstract static class Sync extends AbstractQueuedSynchronizer {
        // ...
    }
    
    // 非公平锁
    static final class NonfairSync extends Sync {
        final void lock() {
            // 1. 先尝试CAS抢锁
            if (compareAndSetState(0, 1)) {
                // 抢到了,设置当前线程为持有者
                setExclusiveOwnerThread(Thread.currentThread());
            } else {
                // 抢不到,进入AQS的等待队列
                acquire(1);
            }
        }
    }
}

阿西噶阿西:"看到了吗?ReentrantLock底层用的就是AQS!"


🔍 AQS的加锁流程(独占模式)

场景:3个线程竞争同一把锁

线程A:先到,获取锁 ✅
线程B:后到,进入等待队列
线程C:后到,进入等待队列

完整流程图

sequenceDiagram
    participant ThreadA as 线程A
    participant State as state变量
    participant Queue as CLH队列
    participant ThreadB as 线程B
    participant ThreadC as 线程C

    Note over ThreadA,ThreadC: 初始状态:state=0(未锁定)
    
    ThreadA->>State: 1. CAS(0 → 1)
    State->>ThreadA: 成功,获取锁 ✅
    
    ThreadB->>State: 2. CAS(0 → 1)
    State->>ThreadB: 失败(state=1)
    ThreadB->>Queue: 3. 加入等待队列
    Note over Queue: head → [ThreadB] ← tail
    ThreadB->>ThreadB: 4. park(阻塞)
    
    ThreadC->>State: 5. CAS(0 → 1)
    State->>ThreadC: 失败(state=1)
    ThreadC->>Queue: 6. 加入等待队列
    Note over Queue: head → [ThreadB] → [ThreadC] ← tail
    ThreadC->>ThreadC: 7. park(阻塞)
    
    Note over ThreadA: 执行业务逻辑
    ThreadA->>State: 8. unlock: state=1 → 0
    ThreadA->>Queue: 9. 唤醒head.next(ThreadB)
    Queue->>ThreadB: 10. unpark(唤醒)
    ThreadB->>State: 11. CAS(0 → 1)
    State->>ThreadB: 成功,获取锁 ✅
    
    Note over ThreadB: 执行业务逻辑
    ThreadB->>State: 12. unlock: state=1 → 0
    ThreadB->>Queue: 13. 唤醒ThreadC
    Queue->>ThreadC: 14. unpark
    ThreadC->>State: 15. CAS(0 → 1)
    State->>ThreadC: 成功,获取锁 ✅

详细步骤

步骤1:线程A获取锁

// 线程A执行
lock.lock();

// 底层实现
if (compareAndSetState(0, 1)) {  // CAS:state从0改成1
    // 成功,获取锁
    setExclusiveOwnerThread(Thread.currentThread());  // 记录持有者
}

state变化

state: 0 → 1(已锁定)
持有者:Thread-A

步骤2:线程B竞争锁失败,加入队列

// 线程B执行
lock.lock();

// 底层实现
if (compareAndSetState(0, 1)) {  // CAS失败(state=1)
    // 失败,加入等待队列
    acquire(1);
}

// acquire方法
public final void acquire(int arg) {
    if (!tryAcquire(arg)) {  // 再次尝试获取锁,失败
        // 加入等待队列
        addWaiter(Node.EXCLUSIVE);  // 创建Node节点,加入队列
        // 阻塞线程
        LockSupport.park(this);
    }
}

队列状态

head  [Node(Thread-B)]  tail

Node结构:
{
  thread: Thread-B,
  prev: head,
  next: null,
  waitStatus: 0
}

步骤3:线程C也加入队列

head → [Node(Thread-B)] → [Node(Thread-C)] ← tail

步骤4:线程A释放锁

// 线程A执行
lock.unlock();

// 底层实现
public void unlock() {
    release(1);
}

public final boolean release(int arg) {
    if (tryRelease(arg)) {  // state:1 → 0
        Node h = head;
        if (h != null && h.waitStatus != 0) {
            unparkSuccessor(h);  // 唤醒head的下一个节点(Thread-B)
        }
        return true;
    }
    return false;
}

唤醒流程

1. state:1 → 0(释放锁)
2. 唤醒head.next(Thread-B)
3. LockSupport.unpark(Thread-B)
4. Thread-B从park()返回,继续执行
5. Thread-B尝试CAS获取锁,成功 ✅

🎯 手写一个简易版ReentrantLock

哈吉米:"理解了流程,能不能自己实现一个?"

南北绿豆:"来,100行代码实现简易版!"

/**
 * 简易版ReentrantLock(基于AQS思想)
 */
public class SimpleLock {
    
    // state变量(0=未锁定,1=已锁定)
    private volatile int state = 0;
    
    // 持有锁的线程
    private Thread exclusiveOwnerThread;
    
    // 等待队列(双向链表)
    private final Queue<Thread> waitQueue = new ConcurrentLinkedQueue<>();
    
    /**
     * 加锁
     */
    public void lock() {
        // 尝试CAS获取锁
        if (compareAndSetState(0, 1)) {
            // 成功,记录持有者
            exclusiveOwnerThread = Thread.currentThread();
            return;
        }
        
        // 失败,加入等待队列
        waitQueue.offer(Thread.currentThread());
        
        // 自旋等待(简化版,真实AQS用park)
        while (true) {
            // 如果队列头是自己,尝试获取锁
            if (waitQueue.peek() == Thread.currentThread()) {
                if (compareAndSetState(0, 1)) {
                    // 成功,出队
                    waitQueue.poll();
                    exclusiveOwnerThread = Thread.currentThread();
                    return;
                }
            }
            
            // 继续等待
            LockSupport.parkNanos(1000000);  // park 1ms
        }
    }
    
    /**
     * 解锁
     */
    public void unlock() {
        if (Thread.currentThread() != exclusiveOwnerThread) {
            throw new IllegalMonitorStateException("当前线程未持有锁");
        }
        
        // 释放锁
        exclusiveOwnerThread = null;
        state = 0;  // volatile写,保证可见性
        
        // 唤醒等待队列的第一个线程
        Thread next = waitQueue.peek();
        if (next != null) {
            LockSupport.unpark(next);
        }
    }
    
    /**
     * CAS操作(简化版)
     */
    private boolean compareAndSetState(int expect, int update) {
        // 实际用Unsafe.compareAndSwapInt
        // 这里简化为synchronized
        synchronized (this) {
            if (state == expect) {
                state = update;
                return true;
            }
            return false;
        }
    }
}

测试代码

public class SimpleLockTest {
    
    private static final SimpleLock lock = new SimpleLock();
    private static int count = 0;
    
    public static void main(String[] args) throws InterruptedException {
        // 100个线程并发执行
        CountDownLatch latch = new CountDownLatch(100);
        
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    lock.lock();
                    try {
                        count++;
                    } finally {
                        lock.unlock();
                    }
                }
                latch.countDown();
            }).start();
        }
        
        latch.await();
        
        System.out.println("最终count: " + count);
        // 输出:最终count: 100000(正确)
    }
}

阿西噶阿西:"看,100行代码就实现了一个基本可用的锁!"

哈吉米:"原来AQS的核心就是:state变量 + 等待队列 + CAS!"


🔍 AQS的两种模式

独占模式(Exclusive)

特点:同一时刻只有一个线程能获取锁。

示例:ReentrantLock

// 独占模式
lock.lock();  // 线程A获取锁
// 线程B、C、D全部等待
lock.unlock();  // 线程A释放,线程B获取

共享模式(Shared)

特点:同一时刻多个线程可以获取锁。

示例:CountDownLatch、Semaphore

// 共享模式(Semaphore,允许3个线程)
Semaphore semaphore = new Semaphore(3);

// 线程A、B、C同时获取 ✅
semaphore.acquire();  // state: 3 → 2 → 1 → 0

// 线程D等待
semaphore.acquire();  // 等待(state=0)

对比

模式state含义获取锁条件示例
独占模式0=未锁定,1=已锁定state == 0ReentrantLock
共享模式剩余资源数state > 0Semaphore、CountDownLatch

🎯 公平锁 vs 非公平锁

非公平锁(默认,性能好⭐⭐⭐⭐⭐)

ReentrantLock lock = new ReentrantLock();  // 默认非公平

// 获取锁流程
public void lock() {
    // 1. 先CAS抢锁(不管队列)
    if (compareAndSetState(0, 1)) {
        // 抢到了,直接获取 ✅
        return;
    }
    
    // 2. 抢不到,再加入队列
    acquire(1);
}

特点:新来的线程可以"插队"抢锁。

时序图

sequenceDiagram
    participant ThreadA as 线程A(持有锁)
    participant Queue as 等待队列
    participant ThreadB as 线程B(队列头)
    participant ThreadC as 线程C(新来的)

    Note over Queue: [ThreadB] 在队列等待
    
    ThreadC->>ThreadC: 1. 刚到,尝试CAS
    ThreadA->>ThreadA: 2. unlock(释放锁)
    
    par 同时竞争
        ThreadB->>ThreadB: 尝试获取锁
        ThreadC->>ThreadC: CAS抢锁(插队)
    end
    
    ThreadC->>ThreadC: 3. 抢到了 ✅
    Note over ThreadB: 继续等待(被插队了)

优点

  • ✅ 性能好(减少线程切换)
  • ✅ 吞吐量高

缺点

  • ❌ 可能饿死(队列头的线程一直抢不到)

公平锁(性能差,保证公平⭐⭐⭐)

ReentrantLock lock = new ReentrantLock(true);  // 公平锁

// 获取锁流程
public void lock() {
    // 1. 检查队列是否有等待的线程
    if (hasQueuedPredecessors()) {
        // 有等待的线程,不能插队,直接加入队列
        acquire(1);
    } else {
        // 没有等待的线程,尝试CAS
        if (compareAndSetState(0, 1)) {
            return;
        } else {
            acquire(1);
        }
    }
}

特点:严格按FIFO顺序获取锁,不允许插队。

优点

  • ✅ 公平(不会饿死)

缺点

  • ❌ 性能差(需要线程切换)

性能对比

测试:100个线程,每个线程执行1000次加锁

锁类型执行时间吞吐量
非公平锁1.2秒83333 ops/s
公平锁3.8秒26315 ops/s

性能差距:非公平锁快3倍!

南北绿豆:"所以默认用非公平锁,性能更好。"


🎯 ReentrantLock vs synchronized

对比表

特性synchronizedReentrantLock
层级JVM层(关键字)JDK层(类)
锁释放自动释放手动释放(finally)
公平性非公平可选(公平/非公平)
中断响应❌ 不支持✅ 支持(lockInterruptibly)
超时获取❌ 不支持✅ 支持(tryLock)
条件队列1个(wait/notify)多个(Condition)
性能⭐⭐⭐⭐(JDK 6+优化后)⭐⭐⭐⭐

使用场景

用synchronized

// 简单场景
public synchronized void method() {
    // ...
}

// 或者
synchronized (lock) {
    // ...
}

用ReentrantLock

// 需要高级特性
// 1. 可中断
try {
    lock.lockInterruptibly();  // 可中断
} catch (InterruptedException e) {
    // 处理中断
}

// 2. 超时获取
if (lock.tryLock(3, TimeUnit.SECONDS)) {
    try {
        // 获取到锁
    } finally {
        lock.unlock();
    }
} else {
    // 超时,放弃
}

// 3. 多个条件队列
Condition notFull = lock.newCondition();
Condition notEmpty = lock.newCondition();

🎓 面试标准答案

题目:AQS的核心原理是什么?

答案

AQS(AbstractQueuedSynchronizer)核心

三大组件

  1. state变量:同步状态(volatile int)
  2. CLH队列:等待线程的双向链表
  3. Node节点:封装等待线程

加锁流程

  1. CAS尝试将state从0改成1
  2. 成功 → 获取锁
  3. 失败 → 加入CLH队列,park阻塞

解锁流程

  1. state从1改成0
  2. 唤醒队列头的下一个线程
  3. 被唤醒的线程继续竞争锁

两种模式

  • 独占模式:ReentrantLock
  • 共享模式:Semaphore、CountDownLatch

题目:ReentrantLock和synchronized的区别?

答案(见对比表):

核心区别

  1. synchronized是关键字,ReentrantLock是类
  2. ReentrantLock支持公平锁、可中断、超时获取
  3. ReentrantLock支持多个Condition
  4. synchronized自动释放,ReentrantLock需要手动unlock

选型

  • 简单场景:synchronized
  • 需要高级特性:ReentrantLock

🎉 结束语

晚上10点,哈吉米终于搞懂了AQS的原理。

哈吉米:"原来AQS就是:state变量控制状态,CLH队列存等待线程,CAS保证原子性!"

南北绿豆:"对,ReentrantLock、CountDownLatch、Semaphore底层都是AQS,只是对state的定义不同。"

阿西噶阿西:"记住:state表示资源,队列存等待者,CAS保证并发安全。"

哈吉米:"还有公平锁和非公平锁的区别,非公平锁性能好3倍!"

南北绿豆:"对,下次我们讲CountDownLatch和Semaphore,都是基于AQS实现的!"


记忆口诀

AQS核心三组件,state队列和节点
CAS修改state值,成功获锁失败排
CLH队列双向链,头部唤醒按顺序
独占模式一个占,共享模式多个进
非公平锁性能好,公平锁防饿死


希望这篇文章能帮你轻松掌握AQS的核心原理!理解了AQS,就理解了整个JUC包!💪