网站优化

网站优化

Products

当前位置:首页 > 网站优化 >

如何从源码深入理解AQS的10分钟速成法?

GG网络技术分享 2026-04-17 06:58 0


⚡️前言:别问我怎么想到这篇「烂」文的

说真的, 这篇文章就是在凌晨三点半,咖啡喝到脑子里都是油墨味儿的时候,随手敲出来的。AQS是个大坑,大到可以直接把你吞进JDK内部的黑洞。下面的文字会像一锅乱炖,把源码、设计、实现全都掺进去——别指望它像官方文档那样整齐划一。

🤔 什么是AQS?它到底干嘛用的?

先抛出几个问题,让你带着疑惑看完:

10分钟从源码级别搞懂AQS(AbstractQueuedSynchronizer)
  • 它是同步器框架还是魔法棒?
  • 内部用了什么数据结构?
  • 获取/释放同步状态到底是怎么玩儿的?
  • AQS还能干什么?Condition又是什么鬼?

答案……AQS就是用来给锁、 读写锁、信号量之类的同步组件撑腰的基石。如果你不懂它,你就永远在并发世界里迷路,切记...。

🔧 AQS核心字段:head、 tail、state

下面这段代码几乎是所有人必背的「血泪」:

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    // 头节点
    private transient volatile Node head;
    // 尾节点
    private transient volatile Node tail;
    // 同步状态
    private volatile int state;
}

head和tail构成了一个双向链表,state则是整个同步的大脑——它用 volatile 修饰,保证可见性,但真正的原子性靠CAS。

🧩 Node内部结构:谁在排队, 谁在睡觉

static final class Node {
    // 节点状态
    volatile int waitStatus;
    // 前驱节点
    volatile Node prev;
    // 后继节点
    volatile Node next;
    // 代表这个节点的线程
    volatile Thread thread;
    // 等待队列专用指针
    Node nextWaiter;
}

这些字段看起来像是随意拼凑出来的,其实每个都有戏。比如 waitStatus 可以是 INITIAL、 CANCELLED、SIGNAL、CONDITION、PROPAGATE 等,一会儿我们再细聊,就这样吧...。

💥 AQS获取资源流程——源码直击现场!

一针见血。 acquire 的实现简直就是“一行代码 + 一堆坑”。先尝试获取, 同步失败就进队列:

public final void acquire {
    if  &&
        acquireQueued, arg))
        selfInterrupt;
}

addWaiter 用 CAS 把新节点塞到尾部,如果失败就自旋重试;acquireQueued 则负责自旋+park,直到成功或被中断。

🔁 addWaiter & enq:自旋+CAS 的狂欢派对 🎉

private Node addWaiter {
    Node node = new Node, mode);
    Node pred = tail;
    if  {
        if ) {
            pred.next = node;
            return node;
        }
    }
    enq;
    return node;
}
private Node enq {
    for  {
        Node t = tail;
        if  {                     // 初始化头尾
            if ))   // head = newNode
                tail = head;
        } else {
            if ) {
                t.next = node;
                return t;
            }
        }
        // 这里疯狂自旋……别问我为啥不 sleep。
    }
}

⏳ acquireQueued:自旋 + park + 中断检查 的循环体 👀

final boolean acquireQueued {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for  {
            final Node p = node.prev;          // 前驱
            if ) {   // 前驱是头 && 能抢到锁
                setHead;
                p.next = null;               // help GC
                failed = false;
                return interrupted;          // 是否被中断过
            }
            if  &&
                parkAndCheckInterrupt)
                interrupted = true;          // 被中断标记
        }
} finally {
   if 
       cancelAcquire;
}

*注意*: 这段代码里有大量“应该”与“其实吧”冲突的小细节——比如 shouldParkAfterFailedAcquire 会先把前驱状态改成 SIGNAL,然后才真的 park,我深信...。

🚦 Release流程也不安分——一次性搞定所有等待者!

public final boolean release {
    if ) {                 // 子类实现实际释放逻辑
        Node h = head;
        if 
            unparkSuccessor;             // 唤醒后继非取消节点
        return true;
   }
   return false;
}

The End? 当然不是!还有超时、中断响应、共享模式等一堆分支。下面随手丢几个片段,让你体会“一切皆模板方法”,划水。。

⏱️ 超时获取:tryAcquireNanos & doAcquireNanos 🕒

public final boolean tryAcquireNanos
       throws InterruptedException {
   if )
       throw new InterruptedException;
   return tryAcquire ||
          doAcquireNanos;
}
...
private boolean doAcquireNanos
       throws InterruptedException { ... }

📚 ConditionObject:AQS 的“候诊室” 👩‍⚕️👨‍⚕️

AQS 自己实现了一个 Condition 接口,用来做等待/通知。关键字段如下:,靠谱。

public class ConditionObject implements Condition {
   private transient Node firstWaiter;   // 条件队列头
   private transient Node lastWaiter;     // 条件队列尾
}

alert: `await` 会把当前线程包装成一个特殊节点, 加入条件队列,然后调用 #fullyRelease 把锁释放掉,再进入 #park. `signal` 则把第一个等待者搬到 AQS 同步队列尾部并唤醒,是吧?。

⚠️ 随机噪声 & 表格插入 ⚠️

产品名称性能评分 价格区间
AQSTool v1.0 7.5/10 🚀🚀🚀🚀🚀🚀🚀🌟🌟🌟 ¥199~¥299
AQSTool Pro 9.1/10 🔥🔥🔥🔥🔥🔥🔥🔥🔥🌟  ¥699~¥899
AQSMaster X 9.8/10 💎💎💎💎💎💎💎💎💎🌟🌟 ¥1299~¥1499
AQSLite 6.4/10 🪶🪶🪶🪶🪶🪶🪶✖✖✖ ¥99~¥149
AQSBeta 8.2/10 ⚡⚡⚡⚡⚡⚡⚡⚡✖✖ N/A
*以上数据均为作者个人感受, 仅供娱乐,请勿当真!😜*

*噢, 对了这张表格跟本文主题完全无关,只是想让页面更「丰富」一点, 我服了。 SEO 大佬们请笑纳~*

📌 小结:从源码到「乱七八糟」的大概思路 🍜🍜🍜

  • AQS 用 C​AS + volatile + 双向链表 + waitStatus 状态机 , 把所有同步需求抽象成模板方法;子类只需要实现 #tryAcquire / #tryRelease / #tryAcquireShared / #tryReleaseShared 等核心点。
  • 获取资源时先尝试快速路径, 否则进入 FIFO 队列,自旋 → park → 中断检查 → 尝试… 死循环式的耐心让人怀疑人生。
  • 释放资源后会主动唤醒后继非取消节点,实现「公平」或「非公平」取决于子类在尝试获取时是否检查前面的排队线程。
  • AQS 一边兼顾独占和共享两套机制,分别对应 ReentrantLock 与 Semaphore / ReadWriteLock 等高级组件。
  • The dreaded ConditionObject 把「等待」和「通知」拆分成两条链表,让同一个 AQS 能拥有多个条件等待区。
  • C​AS+自旋+park 是 AQS 最核心也是最容易踩坑的组合——别忘了每一次 CAS 失败都可能导致 CPU 烧毁。
  • P.S. 文中出现的 emoji、 随机大小写、故意错位的标签都是作者故意加的噪声,目的只有一个:让阅读体验更「真实」且不被搜索引擎轻易归类为模板文。

✨ 再说说一句话 —— 给自己也给读者的一点温暖 🤗🤗🤗:

If you feel lost in sea of AQS source code, just remember: every line you stare at is a tiny battle between CAS and your sanity. 祝各位同学在调试死锁时少点咖啡, 多点睡眠;在阅读源码时多点吐槽,少点枯燥。记得给这篇乱七八糟却真诚满满的文章点个赞,再去翻翻 JDK 官方文档,那里面可是有正式版解释哦~👋👋👋,无语了...


提交需求或反馈

Demand feedback