个人观点,仅供参考
一、先搞懂:AQS 到底是什么?
AQS(AbstractQueuedSynchronizer)想象成学校食堂的 “打饭排队” —— 它的核心作用是:让大家有序打饭,不插队、不抢饭,保证食堂秩序(对应并发编程中的 “线程同步”)。
在 Java 并发中,线程就像抢饭的同学,而 “饭菜” 就是 “同步资源”(比如锁、许可)。AQS 的核心逻辑特别简单:
-
先看有没有 “饭菜”(同步状态 state);
-
有就直接打饭(线程获取资源成功);
-
没有就排队等(线程封装成 Node 节点入队);
-
有人打完饭(线程释放资源),就叫下一个人来打(唤醒队列节点)。
二、核心角色拆解
1. 同步状态(state):食堂的 “饭菜份数”
state 是 AQS 里最核心的 “计数器”,用 volatile 修饰(保证大家都能看到最新份数),就像食堂黑板上写的 “今日剩余饭菜份数”:
独占模式:专属单人套餐
就像食堂推出一人份专属套餐,窗口一次只接待 1 个人。
①state=0:窗口空着,还没有人过来(锁空闲)。
②state=1:有同学正在窗口打这份套餐(锁被持有),后面的人只能排队等。
③重入(state=2/3):比如这个同学打完套餐,发现还能加份同款小菜,不用重新排队,直接跟阿姨说 “再加一份”,state 就跟着加 1,相当于 “同一个人多次占用窗口”。
共享模式(Semaphore):自助取餐区
比如食堂的自助取餐区,摆了 5 个餐盘(对应 state=5),规则是 :有空盘才能取,取走少 1 个,放回多 1 个。要是 state 减到 0:餐盘全被拿走了,后面的同学得等有人放回餐盘(释放许可)才能取。
2. 阻塞队列(FIFO):食堂的 “排队队伍”
当饭菜不够时,没抢到的同学要排队 —— 这个队伍就是 AQS 的 “双向阻塞队列”,本质是个双向链表(前后同学能互相找到),队列里的每个人就是 “Node 节点”。

3. Node 节点:排队的 “同学”
每个没抢到资源的线程,都会被 AQS 包装成一个 “Node 节点”(排队的同学),这个 “同学” 身上带了 几个关键信息,帮他顺利排队打饭:
| Node 的核心字段和方法 | “同学” 的属性(比喻) | 通俗解释 |
| thread | 同学本人 | 记录是谁在排队(哪个线程在等待) |
| prev、next | 同学前面和后面排队的人 | 知道自己排队的前面是谁、后面是谁,方便排队和叫号 |
| waitStatus | 同学的 “状态” | 排队打饭同学的状态标签,标记其等待、放弃或等通知的状态。 |
| isShared() | 判断资源是否共享 | 比如汤不限购(共享)、红烧肉限量(独占,谁抢到就是谁的,别人得等) |
三、Node 节点的 “5 种状态”
waitStatus 是 Node 的核心,就像同学排队时的 “状态标签”,决定了他能不能打饭、要不要被叫醒:
- 初始状态(0):刚站到队伍里,还没任何状态(新同学排队)。
- SIGNAL(-1):“等前一个人叫我”—— 比如同学 A 排在前面,同学 B 在后面,B 会跟 A 说 “你打完饭叫我一下”,A 的状态就变成 SIGNAL。
- CANCELLED(1):“不想吃了,退出排队”—— 比如同学等太久不耐烦走了(线程超时 / 中断),这个位置会被标记为 “无效”,后面的人会跳过他。
- CONDITION(-2):等待某个条件—— 比如同学打好饭了,但要等小菜才能吃。
- PROPAGATE(-3):“自助餐,大家一起吃”—— 比如食堂加了新的自助餐,当前同学吃完后,会喊后面所有人 “快来打,还有很多”(共享模式的唤醒传播,比如 Semaphore)。
四、队列操作遵循以下原则:
- 入队:新节点加入队列尾部,前驱节点的waitStatus设为SIGNAL。
- 出队:头节点释放资源后,唤醒有效后继节点(跳过CANCELLED节点)。
- 清理:取消或超时的节点会被移除,保证队列连续性。
五、为什么 AQS 这么高效?
- 不盲目等待:只有前面的人标记了 “叫我”(SIGNAL 状态),才会坐着等,避免无效等待;
- 不浪费位置:有人中途走了(CANCELLED),后面的人会自动跳过,队伍始终是有效队列;
- 先试试再等:入队后不会立刻坐着,会先 “自旋” 几次(再抢一次饭),避免频繁坐下站起来(线程上下文切换);
- 标杆(虚拟头节点)设计:不用每次都判断队伍有没有头,简化排队逻辑,提高效率。
AQS工作原理详解
1166

被折叠的 条评论
为什么被折叠?



