本手册对标大厂JUC面试最高标准,逐层拆解 AQS 底层核心原理、源码流程、队列机制、锁/同步器实现、独占/共享模式、可中断/超时机制、Condition队列、线上坑点、高频面试题。全篇逻辑闭环、无废话、可直接背诵口述。
一、AQS 核心概述(是什么、干什么、地位·面试深挖完整版)
1.1 AQS 定义
AQS(AbstractQueuedSynchronizer,抽象队列同步器),是JDK1.5由Doug Lea重磅设计的JUC并发包底层核心基石框架,是Java并发编程体系的灵魂组件。AQS是一个用于构建锁、同步器、并发控制工具的抽象模板框架,并非锁本身,而是一套标准化的线程同步底层实现规范。
在JDK1.5之前,Java并发同步仅依赖底层JVM实现的synchronized,功能单一、扩展性极差;AQS的诞生彻底统一了Java线程同步的底层实现,所有后续JUC高性能并发工具,全部基于AQS模板衍生实现,彻底盘活了Java高性能并发编程体系。
AQS具备极强的通用性,JUC包下99%的同步工具底层均依赖AQS实现,覆盖独占锁、共享锁、计数器、栅栏、信号量全场景,具体落地组件如下:
-
独占锁:ReentrantLock
-
共享锁:ReentrantReadWriteLock、Semaphore、CountDownLatch
-
并发工具:CyclicBarrier、ThreadPoolExecutor 部分阻塞逻辑
1.2 核心作用
AQS核心设计初衷:抽离所有同步工具的通用底层逻辑,模板化封装重复代码。将线程竞争、资源抢占、队列排队、线程阻塞、唤醒调度、状态更新、失效节点清理等底层复杂、高并发、线程不安全的通用逻辑全部封装在AQS底层。
上层锁与同步器仅需根据自身业务特性,实现少量极简的抽象方法,即可快速拥有完整、高性能、高并发的同步能力,彻底避免重复造轮子,统一并发底层实现标准。
简单来说:AQS负责底层脏活累活,上层锁负责业务规则判断,各司其职、架构解耦,是典型的模板方法模式最佳实践。
1.3 AQS 核心设计思想(必背)
一句话绝杀:基于 state 状态变量 + 双向 FIFO 同步队列 + CAS 自旋抢占 + 挂起唤醒机制,实现线程资源竞争与排队。
1.4 AQS 核心架构特性(核心加分项)
-
模板方法设计模式:底层封装final修饰的核心模板方法(不可重写,保证并发安全),对外暴露可重写的自定义抽象方法,灵活适配不同同步场景
-
无锁并发设计:全程基于CAS+volatile实现状态更新,不依赖重量级锁,高并发性能极致优越
-
线程排队机制:通过CLH双向FIFO队列,精准管理阻塞线程,避免线程无序竞争、CPU空转
-
双工作模式:原生支持独占、共享两种同步模式,覆盖所有锁场景
-
全能力适配:原生支持公平/非公平、可中断、超时阻塞、精准条件唤醒,功能全面碾压synchronized
1.5 AQS 核心模板方法与自定义方法(面试必区分)
1.5.1 底层核心模板方法(final 禁止重写)
AQS底层核心调度方法,封装完整排队、阻塞、唤醒逻辑,所有同步器统一复用,不可篡改:
-
acquire():独占模式加锁核心模板
-
acquireShared():共享模式加锁核心模板
-
release():独占模式释放资源模板
-
releaseShared():共享模式释放资源模板
1.5.2 上层自定义抽象方法(交由子类实现)
仅做简单的资源状态判断,逻辑极简,由具体锁/同步器根据自身规则实现:
-
tryAcquire/tryRelease:独占模式自定义抢占、释放逻辑
-
tryAcquireShared/tryReleaseShared:共享模式自定义抢占、释放逻辑
-
isHeldExclusively():判断当前线程是否独占持有资源
1.6 AQS 与 synchronized 核心定位差异(底层认知升级)
-
synchronized:JVM底层C++实现、黑盒机制、功能固定、无法扩展、使用简单
-
AQS:Java代码层纯手工实现、白盒可扩展、功能灵活可定制、性能可控、适配高并发复杂场景
1.7 本节终极面试绝杀总结(可直接背诵)
AQS是JUC体系的底层同步模板框架,通过volatile状态变量、CAS自旋、CLH双向队列、park/unpark阻塞唤醒机制,封装了线程同步的所有通用底层逻辑;采用模板方法模式,底层固定核心调度逻辑,上层自定义资源竞争规则,原生支持独占/共享、公平/非公平、中断/超时、精准唤醒,是所有Java高性能锁与并发工具的实现基石,也是高并发编程的核心底层原理。
二、AQS 三大核心底层结构(源码基石·超全深挖版)
AQS 整套并发调度体系,完全依托State状态变量、CLH双向同步队列、Node节点结构三大核心基石搭建。所有锁竞争、排队、阻塞、唤醒、资源释放逻辑,本质都是这三个结构的状态流转与数据变更。掌握本章源码细节,彻底吃透AQS底层骨架,解决90%的AQS底层面试追问。
2.1 核心状态变量:state(AQS灵魂·全网最细解读)
2.1.1 源码定义
volatile int state,AQS 核心同步状态变量,全局可见、线程间实时感知,是判断资源是否被占用、是否可竞争的唯一标准。
核心特性:volatile 保证多线程可见性、禁止指令重排;所有锁抢占/释放,必须通过 CAS 无锁修改 state,保证并发安全。
2.1.2 不同同步器的 state 语义(面试必背)
state 本身无具体业务含义,不同JUC工具对其做了不同语义定义,这是AQS模板通用性的核心体现:
-
ReentrantLock(独占可重入锁):state=0 代表无锁空闲;state>0 代表线程持有锁,数值为可重入次数,同一线程多次加锁state自增,解锁自减,归0彻底释放锁。
-
ReentrantReadWriteLock(读写锁):int共32位,高16位存读锁状态、低16位存写锁状态,实现读写分离、读共享写独占。
-
Semaphore(信号量):state代表剩余可用许可证数量,线程获取许可证state减1,释放加1,为0时线程阻塞排队。
-
CountDownLatch(倒计时闸门):state代表剩余倒计时次数,每次countDown()state减1,归0后唤醒所有阻塞等待线程。
2.1.3 核心设计原理
AQS 放弃 synchronized 的JVM底层锁标记,改用纯代码层数字状态机管理并发,所有同步逻辑围绕state状态流转,简单、高效、可扩展,这是AQS高性能、可定制的根本原因。
绝杀总结:并发竞争的本质,就是多线程CAS争抢修改state变量的过程。
volatile int state:同步状态,代表资源占用情况,不同锁含义完全不同。
-
ReentrantLock:state=0 无锁,state>0 持有锁(支持可重入)
-
ReadWriteLock:高16位读锁、低16位写锁
-
Semaphore:代表剩余许可证数量
-
CountDownLatch:代表剩余倒数次数
所有锁竞争、释放,本质都是 CAS 修改 state。
2.2 同步队列:CLH双向FIFO队列(排队核心)
2.2.1 队列定位
AQS 内置 CLH变体双向阻塞队列,是所有抢锁失败线程的统一排队容器。当线程CAS抢占state资源失败后,不会自旋空转,而是封装为Node节点入队排队,等待前驱节点释放锁后唤醒。
2.2.2 队列核心属性(源码级)
AQS 维护两个核心指针,标记队列首尾:
-
transient Node head:队列虚拟头节点,不存储业务线程,仅做哨兵占位,统一队列操作逻辑,避免空队列判空复杂度
-
transient Node tail:队列尾节点,新入队节点挂载在tail之后
2.2.3 队列核心特性(面试高频)
-
双向链表结构:prev、next双向指针,支持反向遍历、惰性清理失效节点
-
FIFO先进先出:保证公平排队基础,公平锁严格遵循、非公平锁局部插队
-
无锁入队机制:基于CAS自旋完成尾节点挂载,无需加锁,并发入队安全
-
哨兵头节点设计:永久存在虚拟头节点,简化首尾节点空值判断,统一唤醒逻辑
2.2.4 入队核心流程(极简源码逻辑)
-
抢锁失败,构建当前线程Node节点
-
CAS尝试将节点挂载到队列尾部
-
CAS失败则自旋重试,直至入队成功
-
入队完成后,自旋判断前驱节点状态,满足条件则park挂起
核心优势:通过队列统一管控阻塞线程,避免大量线程无脑自旋抢占CPU,实现并发流量削峰。
抢锁失败的线程,会封装成 Node 节点,进入同步队列排队,自旋+挂起等待唤醒。
队列特性:双向链表、FIFO、公平机制、无锁入队、自旋前置校验。
2.3 Node 节点结构(线程封装载体·核心源码)
Node 是 AQS 队列的最小单元,每一个阻塞线程都会被完整封装为一个Node对象,线程本身不会直接进入队列,通过Node挂载线程信息、状态、指针,实现队列调度。
2.3.1 Node 完整核心属性(逐行解读)
-
volatile int waitStatus:节点等待状态,队列调度的核心,控制线程挂起、唤醒、失效、传播,共5种状态(下文详解)
-
volatile Node prev:前驱节点指针,用于反向遍历、状态校验、失效节点清理
-
volatile Node next:后继节点指针,用于唤醒后继排队线程
-
volatile Thread thread:当前封装的阻塞线程,为空代表哨兵节点/失效节点
-
Node nextWaiter:标记节点模式,区分独占/共享模式;同时用于绑定Condition条件队列,实现双队列流转
2.3.2 关键属性深度辨析(面试坑点)
很多开发者混淆 next 与 nextWaiter:
-
next:同步队列专属指针,双向链表前后关联,用于排队唤醒
-
nextWaiter:模式标记+条件队列指针,不参与同步队列链表排序
每个阻塞线程都会被封装为 Node,核心属性:
-
thread:当前阻塞线程
-
prev / next:前后双向指针
-
waitStatus:节点等待状态(核心!控制挂起与唤醒)
-
nextWaiter:区分独占/共享模式、绑定Condition队列
2.4 waitStatus 五大状态全集(面试绝杀必背)
waitStatus 是AQS队列调度的状态控制器,所有线程挂起、唤醒、取消、传播逻辑均由该字段控制,五大状态各司其职,无冗余设计。
-
0(初始状态):节点刚入队、正常排队,无特殊状态,默认初始值。
-
SIGNAL(-1)(唤醒就绪):整个AQS最重要的状态。代表「当前节点的后继节点需要唤醒」。前驱节点释放资源后,会唤醒后继SIGNAL状态节点;只有前驱为SIGNAL,当前线程才会安全park挂起。
-
CONDITION(-2)(条件等待):节点处于Condition条件队列中,不在同步队列,等待signal唤醒,唤醒后会转移至同步队列重新抢锁。
-
PROPAGATE(-3)(共享传播):仅共享模式生效,代表本次唤醒需要向后持续传播,批量唤醒后续共享节点,最大化利用共享资源。
-
CANCELLED(1)(失效取消):唯一正数状态。线程超时、被中断、异常退出时标记为此状态,节点永久失效、不再参与竞争,AQS会惰性清理该类节点。
2.5 三大结构联动终极原理(核心闭环)
状态变量定资源、队列定顺序、节点定线程:
AQS 通过 state 变量标记资源占用状态,多线程CAS竞争state;竞争失败的线程封装为Node节点,携带waitStatus状态进入CLH双向队列排队;队列根据节点状态完成自旋、挂起、唤醒、失效清理,最终实现有序、安全、高性能的线程同步调度。
2.6 本节面试绝杀总结(直接背诵)
AQS三大底层核心结构:volatile state状态变量作为资源竞争标识,所有锁操作本质是CAS修改state;CLH双向FIFO同步队列管控所有阻塞线程,实现有序排队、无锁入队;Node节点封装阻塞线程与waitStatus状态,通过五大状态控制线程挂起、唤醒、取消与传播。三者联动,构成AQS无锁并发、队列调度、精准唤醒的完整底层能力。
三、AQS 两大工作模式(独占 / 共享)
3.1 独占模式 Exclusive(全网超全深度版)
3.1.1 核心定义
AQS独占模式(排他模式),是指同一时间仅允许单个线程占有同步资源,其他竞争线程必须进入同步队列阻塞排队,直至持有资源的线程完全释放锁。资源具有排他性、独占性,无并发共享能力,是最经典的锁竞争模型。
独占模式严格遵循 一对一占用、一对一唤醒 机制:一个线程持有锁、释放锁时仅唤醒一个后继排队线程,不会批量唤醒,彻底保证资源独占排他特性。
3.1.2 典型应用场景与落地组件
适用于临界资源互斥访问场景,保证同一时刻只有一个线程操作共享变量、修改业务数据,解决并发写竞争问题。
核心落地组件:ReentrantLock(可重入独占锁)、ReentrantReadWriteLock.WriteLock(写锁)。
3.1.3 独占模式核心抽象方法(子类实现)
独占模式所有资源竞争逻辑,由子类自定义实现,AQS仅负责排队、阻塞、唤醒兜底逻辑:
-
tryAcquire():尝试抢占独占资源,返回true表示抢锁成功,false抢锁失败入队
-
tryRelease():尝试释放独占资源,返回true表示资源完全释放,可唤醒后继线程
-
isHeldExclusively():判断当前线程是否持有独占资源,用于重入校验、状态判断
3.1.4 模板方法完整执行逻辑(final不可重写)
AQS底层封装final修饰的核心模板方法,固定独占模式调度流程,所有独占锁统一复用,无法篡改:
① acquire() 独占加锁核心模板(不响应中断)
执行逻辑:先尝试快速抢锁 → 失败则封装节点入队 → 自旋竞争 → 满足条件挂起,全程不响应线程中断,被中断后仅标记状态,不抛出异常,继续排队抢锁。
// 独占模式加锁入口(不响应中断)
public final void acquire(int arg) {
// 1. 尝试快速获取锁:成功直接返回
// 2. 失败则执行:入队 + 自旋 + 挂起阻塞
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// 排队过程中若被中断,最终自行补中断标记
selfInterrupt();
}
② acquireQueued() 队列自旋核心方法
节点入队后核心自旋逻辑,是独占模式性能核心:前置有限自旋、避免频繁内核挂起。
核心流程:
-
获取当前节点的前驱节点,若前驱是头节点(队列首个有效排队节点),再次尝试抢锁
-
抢锁成功:将当前节点设为新头节点,移除旧头节点,线程出队执行业务
-
抢锁失败:判断前驱节点waitStatus是否为SIGNAL(-1)
-
前驱为SIGNAL:安全park挂起,等待前驱唤醒
-
前驱非SIGNAL:循环自旋重试,持续尝试抢锁、修正节点状态
③ release() 独占解锁核心模板
独占模式释放锁唯一入口,解锁成功后精准唤醒单个后继线程,不批量传播。
// 独占模式释放锁入口
public final boolean release(int arg) {
// 1. 子类自定义逻辑释放资源、修改state
if (tryRelease(arg)) {
Node h = head;
// 2. 头节点有效且存在等待唤醒的后继节点
if (h != null && h.waitStatus != 0)
// 3. 精准唤醒单个后继排队线程
unparkSuccessor(h);
return true;
}
return false;
}
3.1.5 独占模式可重入原理(ReentrantLock核心)
AQS本身不直接实现可重入,由子类ReentrantLock自定义tryAcquire逻辑实现重入:
-
抢占锁时判断:当前持有锁的线程是否为自身线程
-
是当前线程:state状态值自增,直接加锁成功,无需排队
-
解锁时:state逐层自减,只有state归0才完全释放锁、允许唤醒后继线程
3.1.6 独占模式公平锁 & 非公平锁差异
两种模式仅在tryAcquire抢锁逻辑有差异,排队、唤醒、释放逻辑完全一致:
-
非公平独占锁(默认):线程进来直接CAS抢锁,无视队列排队线程,允许插队,吞吐量高,存在线程饥饿风险
-
公平独占锁:抢锁前先判断同步队列是否有排队节点,有则放弃插队、主动入队,严格FIFO,无饥饿问题,吞吐量略低
3.1.7 独占模式核心特性总结
-
资源排他:同一时刻仅单个线程持有资源,绝对互斥
-
一对一唤醒:解锁只唤醒一个后继节点,无批量传播机制
-
支持可重入:子类可自定义实现线程重复加锁能力
-
隔离性强:彻底杜绝并发竞争,保证临界资源数据一致性
3.1.8 独占模式面试高频绝杀问答
Q:独占模式为什么不能批量唤醒线程?
A:独占资源同一时刻仅能被一个线程持有,批量唤醒会导致多线程同时竞争锁,产生无效竞争、浪费CPU资源,因此AQS独占模式仅一对一精准唤醒,保证高效互斥。
Q:独占模式挂起的核心条件是什么?
A:当前节点前驱节点waitStatus为SIGNAL(-1),才会park挂起,保证一定有前驱节点负责唤醒自己,避免线程永久阻塞。
Q:独占锁和synchronized互斥的核心区别?
A:独占模式基于Java代码层CAS+队列实现,支持公平/非公平、可中断、超时、可重入,灵活可扩展;synchronized是JVM底层实现,功能固定、无法定制,灵活性极差。
3.2 共享模式 Shared(全网超全深度版)
3.2.1 核心定义
AQS 共享模式,是指同一时刻允许多个线程同时持有同步资源,资源不具备排他性,适用于读多写少、资源可复用、限流配额等场景。
共享模式最核心特征:支持唤醒传播。独占锁是“一对一唤醒”,共享锁是“唤醒后向后接力传播”,只要剩余资源充足,会持续批量唤醒后续排队的共享线程,最大化压榨系统并发能力。
3.2.2 典型应用场景与落地组件
适用于资源可共享、并发读取、配额限流、批量放行场景:允许多线程同时操作资源,互不冲突。
核心落地组件:
-
ReentrantReadWriteLock.ReadLock(读锁):海量并发读、读写分离
-
Semaphore(信号量):接口限流、配额控制、资源池并发控制
-
CountDownLatch(倒计时闸门):多线程并行等待、批量放行
3.2.3 共享模式核心抽象方法(子类实现)
共享模式的资源抢占、释放逻辑由子类自定义,AQS只负责队列排队、阻塞、唤醒传播兜底:
-
tryAcquireShared():尝试抢占共享资源,返回值含义特殊: 负数:抢占失败,需要入队阻塞
-
0:抢占成功,无剩余资源,不继续传播唤醒
-
正数:抢占成功,还有剩余资源,需要向后传播唤醒
tryReleaseShared():尝试释放共享资源,返回true表示释放成功、需要触发后继唤醒传播
3.2.4 模板方法完整执行逻辑(final不可重写)
AQS底层封装final共享模板方法,固定共享调度逻辑,核心区别于独占模式:支持批量传播唤醒。
① acquireShared() 共享加锁核心模板
先快速尝试获取共享资源,失败后入队自旋、挂起等待,全程支持资源传播放行。
// 共享模式加锁入口
public final void acquireShared(int arg) {
// 返回 <0 代表抢资源失败,进入队列排队
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
② doAcquireShared() 共享队列自旋核心
共享节点入队后自旋逻辑,和独占最大区别:抢锁成功后会判定是否需要传播唤醒后继,不止自己放行,还要带动后续线程放行。
核心流程:
-
抢资源失败,封装SHARED节点入同步队列
-
自旋判断前驱节点状态,前驱为SIGNAL则park挂起
-
被唤醒后再次尝试抢占共享资源
-
抢占成功且有剩余资源,触发传播唤醒机制
-
持续向后唤醒后续共享节点,直至资源耗尽或无有效排队节点
③ releaseShared() 共享解锁核心模板
共享资源释放入口,成功释放后触发唤醒传播,批量放行阻塞线程。
// 共享模式释放资源入口
public final boolean releaseShared(int arg) {
// 子类自定义释放逻辑,修改state资源数
if (tryReleaseShared(arg)) {
// 唤醒并向后传播
doReleaseShared();
return true;
}
return false;
}
3.2.5 共享模式 PROPAGATE(-3) 状态核心原理(面试难点)
PROPAGATE 是共享模式专属状态,解决共享唤醒丢失BUG:
高并发下多个线程同时释放共享资源,可能导致后继节点无法被正常唤醒,JDK1.6新增PROPAGATE(-3)状态,标记当前节点支持无限向后传播唤醒,保证共享资源可以最大限度放行排队线程。
核心作用:保证共享模式下,只要还有剩余资源,队列中所有排队共享线程都有机会被依次唤醒,不会出现线程“假死阻塞”。
3.2.6 共享模式核心特性
-
资源共享并发:多线程可同时获取资源并行执行,无排他互斥
-
传播式唤醒:区别独占一对一唤醒,支持接力批量唤醒,并发吞吐量极高
-
无严格锁归属:独占锁有明确持有线程,共享锁无单一归属线程,资源看剩余配额
-
适配读多写少:完美适配读并发、限流、批量等待场景
3.2.7 共享与独占模式终极对比(面试必背)
|
对比维度 |
独占模式 Exclusive |
共享模式 Shared |
|---|---|---|
|
资源特性 |
排他互斥,单线程占用 |
共享复用,多线程并发占用 |
|
唤醒机制 |
一对一唤醒,只唤醒后继单个节点 |
传播式唤醒,批量接力唤醒 |
|
返回值规则 |
true/false 成功/失败 |
负/0/正 三种状态,控制传播 |
|
专属状态 |
无专属状态 |
PROPAGATE(-3) 传播状态 |
|
典型组件 |
ReentrantLock、写锁 |
读锁、Semaphore、CountDownLatch |
3.2.8 共享模式高频面试绝杀问答
Q:共享模式为什么需要传播唤醒?
A:共享资源允许多线程同时持有,单次释放资源可能剩余多个可用配额,必须向后传播唤醒,才能最大化利用资源、提升并发吞吐量,若只唤醒单个线程会造成资源浪费。
Q:PROPAGATE 状态是干嘛的?
A:JDK1.6修复共享唤醒丢失问题,用于标记节点需要持续向后传播唤醒,避免高并发下共享线程假死、无法被唤醒的BUG。
Q:共享锁为什么不需要线程独占归属?
A:共享锁基于资源配额数量控制并发,而非线程独占;只要state资源充足,多线程均可并行获取,无需独占绑定。
3.3 独占/共享模式核心区别(面试极简必背)
-
独占:唤醒只唤醒后继一个线程,一对一
-
共享:唤醒后会持续向后传播唤醒,批量唤醒可用线程
四、AQS 完整抢锁/释放锁 源码执行流程(超全源码级闭环)
本章为AQS核心灵魂考点,摒弃简略步骤,逐阶段、逐源码、逐状态流转拆解独占/共享模式下完整加锁、解锁全链路。包含快速抢锁、入队、自旋重试、挂起阻塞、唤醒后继、节点出队、队列重构全流程,彻底打通AQS运行底层逻辑,解决90%源码追问。
前置核心前提:AQS所有同步逻辑 = CAS修改state + CLH队列排队 + 有限自旋 + park/unpark阻塞唤醒 + waitStatus状态流转。
4.1 独占模式 加锁完整全流程(acquire源码逐阶拆解)
入口方法:acquire(int arg),不响应中断,中断仅标记不抛异常,是ReentrantLock.lock()底层核心。
阶段1:快速尝试抢占锁(无竞争直接成功)
线程首次抢锁,不进入队列,直接通过子类实现的tryAcquire()CAS修改state抢占资源:
-
成功:state修改完成,直接执行业务代码,无队列操作、无阻塞、零开销
-
失败:说明锁被占用,进入队列排队流程
阶段2:抢锁失败,封装节点入同步队列(addWaiter)
抢锁失败后,调用addWaiter(Node.EXCLUSIVE)创建独占节点,并入队:
-
构建当前线程专属Node节点,标记为EXCLUSIVE独占模式
-
判断队列是否为空、tail尾节点是否不为null
-
队列正常:CAS尝试将当前节点挂载到tail尾部
-
CAS失败(并发竞争入队):进入
enq()自旋死循环重试入队 -
enq自旋核心:空队列初始化虚拟头哨兵节点,再循环CAS挂载新节点,保证百分百入队成功
阶段3:队列自旋抢锁 + 条件挂起(acquireQueued核心)
节点入队后不会立即挂起,AQS采用有限自旋优化,避免频繁内核切换,是高性能关键:
-
获取当前节点的前驱节点pre
-
判断pre是否为head哨兵节点(当前节点是队列第一个有效排队节点)
-
是首节点:再次尝试tryAcquire抢锁 抢锁成功:清空当前节点线程、设置为新head节点,旧head断开GC,线程出队执行业务
-
抢锁失败:继续向下执行,准备挂起
-
非首节点:直接进入状态修正逻辑,不抢锁
-
判断前驱节点waitStatus: pre.waitStatus == SIGNAL(-1):满足挂起条件,执行park()阻塞,等待前驱唤醒
-
pre.waitStatus > 0(CANCELLED):前驱节点失效,断开前驱,向前遍历清理失效节点,继续自旋
-
其他状态:CAS将前驱状态强制修改为SIGNAL,下次循环挂起
核心铁律:AQS线程绝对不会无脑挂起,只有前驱为SIGNAL,才会park,保证一定有节点负责唤醒自己,杜绝线程永久阻塞。
阶段4:被唤醒后重试抢锁
当前驱节点释放锁、unpark唤醒当前线程后:
-
线程解除阻塞,回到自旋循环头部
-
重新判断前驱、重新抢锁
-
抢锁成功直接出队,失败继续挂起排队
4.2 独占模式 释放锁完整全流程(release源码逐阶拆解)
入口方法:release(int arg),独占锁唯一解锁入口,精准唤醒单个后继节点。
阶段1:子类自定义释放资源(tryRelease)
调用子类实现的tryRelease,自减state状态:
-
可重入锁逐层递减重入次数
-
只有state归0,才返回true代表完全释放锁
-
state不为0(重入未解锁完):不唤醒后继线程
阶段2:校验队列,准备唤醒后继
锁完全释放成功后,获取head头节点:
-
head == null / head.waitStatus == 0:无等待线程,无需唤醒,直接返回
-
存在等待节点:执行unparkSuccessor唤醒后继
阶段3:精准唤醒有效后继节点(unparkSuccessor)
-
优先获取head.next直接后继节点
-
若后继节点为空、或已取消(CANCELLED):从tail尾部反向遍历找最靠前的有效节点
-
找到有效节点后,清空head状态、unpark唤醒线程
反向遍历原因:并发入队时next指针可能未及时赋值,正向遍历易漏节点,反向遍历百分百精准。
4.3 共享模式 加锁完整全流程(acquireShared)
核心区别:不只是自己抢锁成功,还要传播唤醒后续所有可放行线程。
阶段1:快速尝试获取共享资源
执行tryAcquireShared,返回三类结果:
-
返回负数:资源不足,抢锁失败,入队阻塞
-
返回0:抢锁成功,无剩余资源,不传播唤醒
-
返回正数:抢锁成功,有剩余资源,需要向后传播唤醒
阶段2:失败入队 doAcquireShared
-
构建SHARED共享节点入同步队列
-
自旋判断前驱状态,满足条件park挂起
-
被唤醒后再次抢占共享资源
-
抢占成功且有剩余资源:执行setHeadAndPropagate
阶段3:共享专属传播机制(核心亮点)
setHeadAndPropagate是共享模式灵魂:
-
将当前节点设为新head
-
判断后继节点为共享模式、且满足唤醒条件
-
持续向后递归传播唤醒,批量放行排队线程
-
高并发下设置PROPAGATE(-3)状态,杜绝唤醒丢失BUG
4.4 共享模式 释放锁完整全流程(releaseShared)
-
子类tryReleaseShared修改state,增加可用资源配额
-
释放成功后调用doReleaseShared
-
循环唤醒后继共享节点
-
只要资源充足、后继为共享节点,就持续传播唤醒
-
最大化利用共享资源,提升系统吞吐量
4.5 独占/共享 流程核心差异总结(面试秒答)
-
独占:单次解锁只唤醒一个后继节点,一对一唤醒,无传播,保证资源排他
-
共享:解锁后接力传播唤醒,批量放行线程,最大化利用剩余资源
-
自旋逻辑:独占抢到锁直接终止;共享抢到锁还要判断是否传播
4.6 本节终极面试绝杀话术(直接背诵)
Q:简述AQS独占加锁全过程?
A:线程先CAS快速抢占state资源,成功直接执行;失败则封装独占节点入CLH队列,入队后有限自旋重试抢锁,修正前驱节点状态;只有前驱为SIGNAL时才park挂起,等待前驱解锁唤醒;被唤醒后再次抢锁,成功后出队、更新头节点、断开旧节点GC,完成加锁。
Q:AQS共享模式和独占模式最大不同?
A:独占模式一对一唤醒,抢到锁即终止流程;共享模式抢到锁后会判断剩余资源,通过传播机制批量唤醒后续共享线程,支持多线程并发放行,同时通过PROPAGATE状态解决高并发唤醒丢失问题。
五、可中断 / 超时机制原理(深挖高阶考点·源码完整版)
普通acquire独占加锁、acquireShared共享加锁不响应线程中断,线程入队阻塞后,即便被中断也只会标记中断状态,继续排队抢锁,不会终止阻塞。为适配高并发超时释放、线程可终止的业务场景,AQS提供两套高阶阻塞机制:可中断阻塞机制、超时阻塞机制。二者彻底解决了普通锁“死等不退出”的痛点,是Lock相较于synchronized的核心优势之一。
核心前置结论:普通锁 = 不可中断、死等;可中断锁 = 中断即抛异常退出;超时锁 = 超时自动取消排队退出,三者底层队列自旋逻辑完全不同。
5.1 可中断锁机制:acquireInterruptibly(面试高频)
5.1.1 核心定义
可中断锁是指线程在排队阻塞过程中,若检测到线程中断信号,会立即终止排队、抛出InterruptedException异常,直接退出阻塞,不再死等锁资源,适用于任务可终止、避免线程卡死的场景。
5.1.2 核心源码与执行流程
入口方法:acquireInterruptibly(int arg),独占可中断加锁入口,共享模式对应acquireSharedInterruptibly。
// 可中断独占加锁核心源码
public final void acquireInterruptibly(int arg)
throws InterruptedException {
// 优先检测线程中断状态,已中断直接抛异常
if (Thread.interrupted())
throw new InterruptedException();
// 快速尝试抢锁
if (!tryAcquire(arg))
// 抢锁失败,进入可中断排队阻塞逻辑
doAcquireInterruptibly(arg);
}
5.1.3 doAcquireInterruptibly 完整底层流程
-
中断前置校验:方法入口优先判断线程中断标记,已中断直接抛异常,不执行抢锁逻辑。
-
失败入队:抢锁失败,封装独占节点入同步队列,与普通锁入队逻辑一致。
-
自旋双重校验:循环自旋抢锁时,每次循环都会检测线程中断状态。
-
中断触发退出:若自旋/阻塞过程中线程被中断,直接抛出异常,终止循环。
-
异常节点失效:抛出异常前,将当前节点waitStatus标记为CANCELLED(1),节点永久失效,后续由惰性清理机制清除。
5.1.4 可中断锁 VS 普通锁 核心差异(必背坑点)
-
普通acquire锁:阻塞期间被中断,不抛异常、不退出,仅标记中断状态,继续排队抢锁,抢锁成功后最终执行selfInterrupt()补中断。
-
可中断acquireInterruptibly锁:阻塞期间被中断,立即抛异常、终止排队、退出阻塞,彻底结束抢锁流程。
5.1.5 适用场景
接口超时熔断、任务主动终止、线程池任务取消、避免大量线程永久阻塞积压。
5.2 超时锁机制:tryAcquireNanos(高阶难点)
5.2.1 核心定义
超时锁是指线程带指定超时时间抢锁、排队阻塞,限定时间内未抢到锁,自动终止排队,节点标记为失效退出,不永久阻塞。同时支持响应中断,兼具超时退出+可中断双重能力。
5.2.2 核心源码与执行流程
入口方法:tryAcquireNanos(int arg, long nanosTimeout)
// 超时可中断加锁核心源码
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
// 优先校验中断
if (Thread.interrupted())
throw new InterruptedException();
// 快速抢锁成功直接返回true
return tryAcquire(arg) ||
// 超时排队阻塞逻辑
doAcquireNanos(arg, nanosTimeout);
}
5.2.3 doAcquireNanos 完整底层流程
-
计时初始化:抢锁失败后,记录当前时间,计算超时截止时间。
-
限时自旋抢锁:进入队列自旋,每次循环剩余超时时间。
-
短时间自旋优化:若剩余超时时间 小于1000纳秒,不执行park阻塞,持续自旋重试,避免短暂阻塞的系统调用开销。
-
超时阻塞挂起:剩余时间充足,前驱为SIGNAL时,执行限时parkNanos阻塞。
-
超时自动退出:阻塞超时自动唤醒,未抢到锁则标记节点为CANCELLED失效,返回false。
-
中断即时退出:阻塞期间检测到中断信号,立即抛异常退出。
5.2.4 超时机制核心优化点(面试深挖)
AQS超时机制并非无脑阻塞固定时间,而是自适应自旋+限时阻塞:超时时间极短时,自旋开销远小于内核阻塞开销,放弃park、直接自旋重试,最大化提升短时抢锁性能。
5.3 三大加锁机制终极对比(必背表格)
|
加锁方式 |
中断响应 |
超时退出 |
核心特性 |
适用场景 |
|---|---|---|---|---|
|
acquire 普通锁 |
不响应,仅标记 |
不支持,永久排队 |
死等锁、无异常、吞吐量稳定 |
常规同步场景 |
|
acquireInterruptibly 可中断锁 |
立即响应、抛异常退出 |
不支持 |
可终止阻塞、避免线程卡死 |
任务可取消、熔断场景 |
|
tryAcquireNanos 超时锁 |
支持响应中断 |
支持超时自动退出 |
限时抢锁、自适应自旋、双重兜底 |
接口限流、超时降级、资源抢占限时场景 |
5.4 失效节点清理机制统一说明
可中断抛出异常、超时抢锁失败的节点,都会被标记为 CANCELLED(1) 失效状态。AQS不主动加锁删除节点,而是采用惰性清理机制:后续排队线程自旋时,自动向前遍历跳过、断开失效节点,无并发安全问题,开销极低。
5.5 本节面试绝杀问答(直接背诵)
Q:普通锁和可中断锁的核心区别?
A:普通acquire阻塞时被中断,仅标记中断状态,继续排队死等锁;可中断锁acquireInterruptibly检测到中断会立即抛异常、终止排队退出,可避免线程永久阻塞。
Q:AQS超时锁为什么短时间不park?
A:park/unpark是内核态系统调用,开销较高;剩余超时时间极短时,用户态自旋重试开销更低,AQS通过自适应自旋优化,提升短时超时抢锁的性能。
Q:超时/中断失败的节点如何处理?
A:统一标记为CANCELLED失效,不会主动删除,依靠后续线程自旋时惰性清理,保证并发安全且性能最优。
六、Condition 条件队列原理(面试重灾区·源码超全完整版)
Condition 是 AQS 体系精准等待/精准唤醒的核心组件,彻底解决 synchronized 原生 wait/notify 唤醒粗糙、无法精准控制、多条件阻塞混乱的痛点。是 ReentrantLock 配套的条件等待工具,也是生产者消费者模型、线程精准调度的核心底层,属于面试高频深挖难点。
核心前置结论:synchronized 只有唯一等待队列、随机唤醒;Condition 支持多条件队列、精准唤醒指定线程、支持超时/可中断,功能全面碾压原生 wait/notify。
6.1 Condition 核心定位与核心优势
6.1.1 核心作用
基于AQS实现条件阻塞、精准唤醒:线程持有锁后,不满足执行业务条件时主动放弃锁、进入条件队列阻塞等待;待业务条件满足后,指定唤醒对应等待线程,无需全员唤醒竞争锁,极大提升并发效率。
6.1.2 对比 Object.wait/notify 核心优势(必背)
-
精准唤醒:Condition 可创建多个条件队列,不同业务场景绑定不同队列,唤醒指定线程;wait/notify 只能随机唤醒、无法精准控制
-
功能丰富:原生支持超时等待、可中断等待、不可中断等待,适配复杂业务;原生wait功能单一
-
并发高效:精准唤醒无无效竞争,避免“惊群效应”;notifyAll 批量唤醒大量无效线程,浪费CPU
-
面向对象:锁与条件解耦,一个锁可绑定多个Condition,适配多条件阻塞场景
6.2 AQS 双队列核心机制(底层灵魂)
很多开发者最大误区:认为AQS只有一个同步队列。实际上AQS运行依赖两套完全独立的队列体系,分工明确、节点可双向流转。
6.2.1 同步队列(Sync Queue)
-
作用:锁竞争排队队列,所有抢锁失败、等待释放锁的线程在此排队
-
节点状态:主要为 SIGNAL、CANCELLED、PROPAGATE 状态
-
出入队规则:加锁失败入队、解锁唤醒出队
-
核心特征:双向链表、带哨兵节点、CAS无锁入队、支持自旋唤醒
6.2.2 条件队列(Condition Queue)
-
作用:条件不满足的阻塞等待队列,专门存放调用 await() 的线程
-
节点状态:节点固定为 CONDITION(-2) 状态
-
出入队规则:await 入队、signal 唤醒后转移至同步队列
-
核心特征:单向链表、无哨兵节点、仅独占模式可用、不支持自旋
6.2.3 双队列核心流转铁律(面试绝杀)
线程永远不会同时存在于两个队列中!
线程阻塞在条件队列时,会彻底脱离同步队列;只有被 signal 唤醒后,才会从条件队列移除,转移进入同步队列排队抢锁,抢到锁后才会真正唤醒执行业务。
6.3 Condition 核心底层属性
每个 Condition 对象内部维护独立的单向条件队列,核心属性:
-
firstWaiter:条件队列队头节点
-
lastWaiter:条件队列队尾节点
依托 Node 节点的 nextWaiter 属性构建单向链表,区别于同步队列的 prev/next 双向指针,结构更轻量化、开销更低。
6.4 await() 完整源码执行流程(超全拆解)
入口方法:Condition.await(),可中断条件等待,核心流程:释放锁 → 入条件队列阻塞 → 被唤醒 → 转移同步队列 → 重新抢锁 → 方法返回。
阶段1:前置校验与节点入队
-
校验当前线程是否持有独占锁,未持有锁直接抛异常(非法监控状态)
-
新建 CONDITION(-2) 状态节点,通过 nextWaiter 挂载到条件队列尾部
-
清空当前节点的prev/next指针,彻底脱离同步队列体系
阶段2:完全释放锁资源(核心关键)
线程进入条件等待前,必须彻底释放所有锁资源(包含可重入锁),将state置0,否则其他线程无法获取锁、无法执行signal唤醒,造成死锁。
阶段3:循环阻塞等待唤醒
-
循环判断节点是否仍在条件队列中
-
未被唤醒:执行 park 阻塞线程,进入休眠状态
-
被唤醒/中断:退出阻塞循环
核心坑点:await 被唤醒不代表条件满足、不代表抢到锁,仅代表节点转移到同步队列!
阶段4:被唤醒后重新抢锁
节点被signal唤醒后,从条件队列转移至同步队列,进入常规自旋抢锁逻辑,抢到锁后await方法才会返回,继续执行业务代码。
6.5 signal() / signalAll() 底层原理
6.5.1 signal() 精准唤醒单个线程
唤醒条件队列队头第一个有效节点,执行节点转移流程:
-
校验当前线程是否持有锁
-
获取条件队列首节点,判断节点有效性
-
将节点从条件队列移除,清空条件状态
-
将节点加入同步队列尾部,等待抢锁执行
6.5.2 signalAll() 批量唤醒所有线程
遍历条件队列所有阻塞节点,全部转移至同步队列,等同于原生 notifyAll,会产生惊群效应,尽量少用。
6.6 节点状态完整流转闭环(必考)
正常完整状态流转:
同步队列正常节点(0/SIGNAL) → 调用await → 转入条件队列(CONDITION(-2)) → 调用signal → 清除CONDITION状态 → 转入同步队列排队 → 抢锁成功出队
6.7 虚假唤醒 底层解决方案(核心考点)
6.7.1 什么是虚假唤醒?
线程无理由被唤醒,并非被signal/signalAll主动唤醒,而是系统底层随机唤醒,此时业务条件并未满足,直接执行业务会出现数据异常。
6.7.2 解决方案(必写代码规范)
条件等待必须使用 while 循环判断条件,禁止使用 if!
// 正确写法:循环校验,杜绝虚假唤醒
while (!业务条件) {
condition.await();
}
// 错误写法:if单次判断,存在虚假唤醒风险
if (!业务条件) {
condition.await();
}
原理:虚假唤醒后while会再次校验条件,不满足则继续阻塞,保证只有条件真正满足才会退出等待。
6.8 Condition 超时/可中断等待机制
-
await():可中断等待:阻塞过程中被中断,直接抛异常退出
-
awaitUninterruptibly():不可中断等待:无视中断标记,必须等待signal唤醒
-
awaitNanos():超时等待:限时阻塞,超时自动唤醒转移同步队列,不抛异常
6.9 高频面试坑点(全网最细避坑)
-
坑点1:调用await/signal 必须先持有锁,否则直接抛异常,和wait/notify规则一致
-
坑点2:await 一定会释放锁,不释放锁会造成死锁,其他线程无法唤醒
-
坑点3:唤醒后不代表拿到锁,只是进入同步队列排队,必须抢到锁才会返回
-
坑点4:条件队列节点状态固定为CONDITION,其他状态节点无效会被清理
-
坑点5:多Condition互不干扰,各自维护独立队列,实现精准业务分区等待
6.10 本节终极面试绝杀问答(直接背诵)
Q:Condition 条件队列和同步队列的关系?
A:双队列独立存在、节点互斥。线程不满足条件时,从同步队列脱离,进入条件队列阻塞;被signal唤醒后,从条件队列转移到同步队列重新抢锁,抢到锁后完成等待。
Q:为什么 Condition 可以精准唤醒?
A:一个锁可创建多个Condition,每个Condition维护独立的条件队列,不同业务阻塞线程归属不同队列,唤醒时只操作指定队列线程,实现精准调度,无惊群效应。
Q:await 为什么必须用 while 循环?
A:规避系统虚假唤醒问题,if单次判断无法二次校验条件,while循环可反复校验,确保业务条件真正满足才退出等待。
Q:await 阻塞过程中锁的状态是什么?
A:await调用时会彻底释放全部锁资源(state归0),保证其他线程可以竞争锁、执行业务逻辑、调用signal唤醒当前线程,彻底避免死锁。
七、公平锁 & 非公平锁底层原理(AQS源码级深挖·面试重灾区完整版)
ReentrantLock、ReentrantReadWriteLock 均基于 AQS 实现两套锁策略:非公平锁(默认)、公平锁。二者底层AQS队列、阻塞唤醒机制完全一致,唯一区别仅在于:抢锁前是否允许新线程插队。
绝大多数面试误区:认为非公平锁是乱抢、无序。真实核心结论:非公平锁仅刚进来的新线程可以插队,一旦进入队列,依然严格遵循FIFO排队、有序唤醒,不会乱序竞争。
前置核心前提:公平/非公平锁差异,只存在于 tryAcquire 快速抢锁阶段,队列排队、自旋、park挂起、唤醒逻辑完全复用AQS统一逻辑,无任何区别。
7.1 核心定义与核心差异总览
7.1.1 非公平锁(Default)
定义:新线程到达临界区,无视队列排队线程,直接CAS插队抢锁,抢到直接执行,抢不到再入队排队。
核心特征:入场可插队、队列FIFO、高吞吐、存在线程饥饿风险。
7.1.2 公平锁
定义:新线程到达临界区,先检查同步队列是否有排队节点,有排队线程则主动入队,禁止任何插队,严格遵循先来先服务。
核心特征:全程FIFO、无线程饥饿、有序安全、吞吐量偏低。
7.2 非公平锁 完整源码执行流程(ReentrantLock默认)
非公平锁核心实现类:NonfairSync,重点在 tryAcquire 插队逻辑。
阶段1:新线程直接插队抢锁(核心精髓)
线程调用 lock() 时,不做队列判断,直接CAS尝试将state从0改为1:
-
CAS成功:直接获取锁执行业务,完全跳过队列流程,零开销
-
CAS失败:说明锁被占用,进入正常AQS入队排队逻辑
阶段2:可重入逻辑判断
CAS抢锁失败后,判断当前锁持有线程是否为自身:是则state++重入成功,否则继续入队。
阶段3:入队后绝对公平
一旦线程插队失败、进入同步队列,后续所有自旋、挂起、唤醒严格FIFO,前驱唤醒后继,绝不插队,保证队列有序性。
非公平锁完整抢锁链路
新线程直达CAS插队抢锁 → 成功直接执行 / 失败入队排队 → 队列内严格有序唤醒
7.3 公平锁 完整源码执行流程
公平锁核心实现类:FairSync,核心差异:多了 hasQueuedPredecessors() 队列前置校验。
阶段1:强制队列前置校验(禁止插队)
线程抢锁前,优先执行hasQueuedPredecessors() 判断同步队列是否存在有效排队线程:
-
队列有线程:放弃CAS抢锁,直接入队排队
-
队列无线程:当前无竞争,CAS抢锁执行
阶段2:无插队、全程FIFO
无论新线程还是唤醒线程,严格按照入队顺序竞争锁,绝对不允许后入线程优先抢占。
公平锁完整抢锁链路
先查队列是否有人排队 → 有则入队、无则抢锁 → 全程严格FIFO有序执行
7.4 核心源码逐行对比(面试必考)
7.4.1 非公平锁 tryAcquire 源码
// 非公平锁:直接CAS插队,无队列判断
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 关键点:不判断队列,直接CAS插队抢锁
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
// 可重入逻辑
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
7.4.2 公平锁 tryAcquire 源码
// 公平锁:多队列前置校验,禁止插队
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 关键点:hasQueuedPredecessors 判断队列是否有前驱等待
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
// 可重入逻辑和非公平完全一致
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
7.5 hasQueuedPredecessors 底层深度解析(面试深坑)
该方法是公平锁核心,很多人只会背概念,不懂底层判断逻辑:
作用:判断当前队列是否存在有效排队前驱线程,true=有人排队,false=无人排队。
返回true三种场景(禁止抢锁、必须排队):
head != tail:队列存在有效排队节点h
ead.next != null:存在第一个有效等待线程队列首节点线程
!= 当前线程:前面有人比我先来
返回false场景(允许抢锁):队列为空 / 队列只剩哨兵节点,无真实排队线程。
7.6 为什么默认是非公平锁?性能碾压根源
大厂高频追问:为什么JDK默认锁是非公平锁?
非公平锁性能高的三大核心原因
-
减少线程阻塞唤醒开销(最核心) 锁释放瞬间,队列尾部线程还未被park阻塞、新线程直接CAS插队抢到锁,省去一次线程挂起+内核唤醒的昂贵系统调用,用户态直接完成抢锁,性能极高。
-
充分利用CPU时间片 新线程刚进入临界区,CPU缓存、线程上下文均活跃,直接执行无需切换,最大化利用CPU资源。
-
降低队列流转频率 大量短任务可直接插队执行,无需入队排队,减少队列节点创建、CAS入队、GC开销。
7.7 线程饥饿问题深度剖析
7.7.1 非公平锁饥饿原理
高并发短任务场景下,源源不断的新线程持续插队成功,队列中老旧排队线程长期抢不到锁,一直阻塞排队,产生线程饥饿,极端情况下永久无法执行。
7.7.2 公平锁彻底杜绝饥饿
严格FIFO,先来先执行,无论新线程并发多高,都必须排队,保证所有线程都能有序获取锁,无饥饿风险。
7.8 公平锁 & 非公平锁 终极对比表(必背)
|
对比维度 |
非公平锁(默认) |
公平锁 |
|---|---|---|
|
抢锁规则 |
新线程直接CAS插队,队列内FIFO |
全程禁止插队,严格FIFO |
|
核心判断 |
无队列前置校验 |
hasQueuedPredecessors 前置校验 |
|
性能吞吐量 |
极高,减少线程切换开销 |
较低,多队列判断、排队耗时 |
|
线程饥饿 |
存在饥饿风险 |
完全无饥饿 |
|
适用场景 |
高并发、短任务、追求吞吐量 |
低并发、长任务、追求公平有序 |
|
底层共性 |
队列、自旋、park、唤醒逻辑完全一致 |
同左,仅抢锁前置判断不同 |
7.9 高频面试深坑避坑(全网最全)
-
坑点1:非公平锁不是乱序!只有新入场线程可以插队,队列内部绝对有序,不会出现队列乱唤醒、乱竞争。
-
坑点2:公平锁不是绝对公平!仅保证抢锁顺序公平,不保证线程调度CPU时间公平。
-
坑点3:可重入逻辑完全一致!公平/非公平锁重入规则无任何区别,差异仅在首次抢锁。
-
坑点4:唤醒机制完全一致!都是前驱唤醒后继,无批量插队唤醒差异。
7.10 本节终极面试绝杀话术(直接背诵)
Q:公平锁和非公平锁底层唯一区别?
A:唯一区别在 tryAcquire 快速抢锁阶段:非公平锁新线程直接CAS插队抢锁;公平锁会先执行hasQueuedPredecessors判断队列是否有排队线程,有则入队禁止插队。队列排队、自旋、唤醒、阻塞逻辑完全一致。
Q:为什么默认使用非公平锁?
A:非公平锁允许新线程直接插队,省去大量线程park/unpark内核切换开销,CPU利用率高、吞吐量大;虽然存在线程饥饿风险,但绝大多数高并发业务优先保障性能,饥饿问题可通过业务优化规避。
Q:非公平锁队列是有序的吗?
A:完全有序。仅新入场线程可插队,一旦进入同步队列,严格遵循FIFO先进先出,有序唤醒、有序竞争,不会乱序。
Q:什么场景必须用公平锁?
A:长任务、低并发、对线程执行顺序有强一致性要求、不允许线程饥饿的业务场景,优先保证执行公平性而非吞吐量。
八、AQS 核心难点深挖(大厂终面追问·源码级破局)
本章汇总AQS最高频、最容易翻车、面试官压轴追问的底层难点,摒弃浅层概念,从设计初衷、源码逻辑、底层BUG、性能取舍、并发安全角度深度拆解,覆盖绝大多数JUC高阶面试难点,彻底拉开面试差距。
8.1 AQS 整体核心设计思想(终极一句话内核)
AQS核心设计范式:CAS无锁快速尝试 + CLH队列兜底阻塞。优先基于用户态CAS自旋抢锁,规避内核切换开销;竞争失败再进入队列排队、park阻塞,兼顾高并发性能与并发安全,是“乐观抢占、悲观排队”的经典并发设计。
所有AQS底层逻辑、机制、坑点,全部围绕这一核心范式展开,所有锁工具的性能差异、规则差异,均是该范式的个性化衍生。
8.2 为什么 AQS 队列必须前置有限自旋,不直接park阻塞?
核心本质:规避用户态→内核态的昂贵切换开销。
park/unpark 属于操作系统内核态系统调用,线程阻塞唤醒、上下文切换、内核调度的开销远大于用户态CPU自旋。高并发短任务场景下,锁的释放速度极快,线程刚入队就可能抢到锁,直接park会造成大量无效内核调用、性能暴跌。
AQS采用有限自适应自旋优化:入队后短暂自旋重试抢锁,大概率在锁释放间隙直接抢占成功,无需阻塞;仅自旋失败、锁长期被占用时,才会park挂起。
绝杀总结:自旋是为了提速、减少阻塞;park是为了兜底、杜绝CPU空转,二者互补实现性能最优解。
8.3 为什么线程必须等待前驱节点为 SIGNAL(-1) 才允许park挂起?
这是AQS保障线程不永久阻塞、唤醒链路不中断的核心铁律,也是90%开发者不懂的底层关键。
底层逻辑拆解
若前驱节点waitStatus为0、PROPAGATE等非SIGNAL状态,代表前驱节点不会主动唤醒后继,此时当前线程直接park,会出现无线程负责唤醒自己的致命问题,导致线程永久阻塞、死锁。
只有前驱节点标记为SIGNAL(-1),代表前驱节点释放资源后,强制触发unpark唤醒后继节点,当前线程park后一定能被唤醒,彻底杜绝线程假死、永久阻塞问题。
源码行为补充
若前驱节点非SIGNAL状态,AQS会通过CAS强制将前驱状态修改为SIGNAL,构建完整唤醒链路后,再执行park阻塞,保证队列唤醒闭环。
8.4 CANCELLED 失效节点为什么采用惰性清理,不主动删除?
核心原因:主动删除节点存在严重并发安全问题,且开销极高。
1. 并发修改风险
AQS同步队列是高并发读写场景,多线程同时入队、出队、取消排队,主动删除双向链表节点,需要加锁保证安全,彻底破坏AQS无锁并发的高性能设计,得不偿失。
2. 惰性清理性能更优
AQS采用边走边清、自旋清理的惰性机制:后续排队线程自旋抢锁时,自动向前遍历,断开所有CANCELLED失效节点,重构链表指针。
该方式无需额外锁、无需额外遍历、无额外性能开销,在业务自旋中顺带完成清理,适配高并发场景。
3. 失效节点不影响核心流程
CANCELLED节点永久失效,不会参与抢锁、唤醒逻辑,仅占用极小内存,不会造成队列阻塞、唤醒异常,无需即时删除。
8.5 共享模式 PROPAGATE(-3) 状态诞生的底层BUG溯源
PROPAGATE是JDK1.6针对性修复共享锁唤醒丢失BUG的专属状态,是共享模式最难理解的底层难点。
BUG场景还原
高并发下,多个线程同时释放共享资源,线程A释放资源唤醒后继节点,新节点抢锁成功后未及时传播唤醒;同时线程B释放资源无后继可唤醒,导致队列中剩余排队共享线程无任何线程唤醒,永久阻塞假死。
PROPAGATE解决方案
通过-3状态标记当前节点具备全局传播唤醒能力,强制要求节点出队后持续向后遍历,只要存在有效共享节点、剩余资源充足,就持续批量唤醒,彻底解决高并发唤醒丢失问题。
核心结论:PROPAGATE只为修复共享锁并发唤醒丢失BUG,独占模式完全用不到该状态。
8.6 AQS 为什么使用双向CLH队列,不使用单向队列?
核心三大设计考量,全部为适配高并发队列调度与惰性清理:
1. 适配失效节点反向清理
大量节点超时、中断取消后,正向遍历无法精准清理零散失效节点,双向队列可通过prev指针从尾部反向遍历,精准过滤、清理失效节点,保证队列有效性。
2. 精准校验前驱状态
线程自旋时需要频繁校验前驱节点waitStatus,双向指针可快速获取前驱节点、判断SIGNAL状态,构建唤醒链路,单向队列无法高效实现。
3. 解决并发入队指针缺失问题
高并发入队时,节点next指针赋值存在延迟,正向遍历容易漏判有效节点;双向队列反向遍历可百分百精准定位有效唤醒节点,规避并发遍历BUG。
8.7 LockSupport.park/unpark 与 Object.wait/notify 核心底层差异
AQS依托LockSupport实现阻塞唤醒,相较于原生wait/notify是本质性升级,也是AQS功能更强的底层根源:
-
锁依赖不同:wait/notify必须依赖synchronized锁,否则抛异常;park/unpark无需任何锁,纯线程级阻塞唤醒。
-
唤醒顺序不同:wait/notify随机唤醒、存在惊群效应;unpark精准唤醒指定线程。
-
时序兼容性不同:park/unpark支持先唤醒、后阻塞(提前存储许可),线程后续park直接返回;wait/notify时序颠倒会永久阻塞。
-
功能扩展性不同:park支持限时阻塞、适配超时锁机制;原生wait功能单一、无法定制。
8.8 AQS 可重入原理底层深挖(为什么不会死锁?)
AQS本身不实现可重入,由子类自定义规则,ReentrantLock可重入的核心是线程归属校验 + state计数累加。
-
同一线程重复加锁:不竞争state、不入队、不阻塞,直接state自增。
-
解锁时逐层递减:只有state归0,才彻底释放锁、唤醒后继线程。
-
规避死锁核心:可重入是线程内部状态叠加,不会触发队列排队与阻塞,彻底杜绝自死锁。
高频坑点:共享锁(Semaphore、CountDownLatch)不支持可重入,仅独占锁可自定义实现可重入逻辑。
8.9 为什么公平锁无法彻底杜绝CPU饥饿?
多数开发者误区:公平锁绝对公平、无线程饥饿。真实底层逻辑:
公平锁仅保证抢锁时序公平(FIFO排队),不保证操作系统线程调度公平。OS线程调度优先级、时间片分配、CPU亲和性依然会导致部分线程获取CPU时间片更少,存在轻微CPU饥饿,仅能杜绝锁竞争饥饿。
8.10 AQS 无锁入队的并发安全原理
AQS入队全程无synchronized、无Lock,依靠CAS自旋 + 哨兵头节点 + 指针重试保证并发安全:
-
新节点优先CAS挂载队尾,成功直接入队;
-
并发竞争CAS失败,进入enq死循环自旋重试;
-
空队列自动初始化哨兵节点,统一入队逻辑,避免空指针并发问题;
-
自旋重试保证百分百入队成功,无并发冲突。
核心总结:AQS所有并发安全,全部依托CAS+自旋实现,纯用户态无锁设计,性能碾压重量级锁。
8.11 独占与共享模式不能混用的底层原因
AQS节点通过nextWaiter标记独占/共享模式,两种模式的唤醒机制、状态流转、队列逻辑完全不兼容:
-
独占模式:一对一精准唤醒,无传播,依赖SIGNAL状态链路;
-
共享模式:批量传播唤醒,依赖PROPAGATE状态,多线程并发放行;
若混用会导致唤醒逻辑错乱、资源计数异常、线程永久阻塞,因此AQS强制一个同步器只能选择一种模式,不支持混合模式。
8.12 本节大厂终极面试绝杀问答(压轴背诵版)
Q:AQS自旋挂起的核心逻辑与性能取舍是什么?
A:优先用户态有限自旋抢锁,减少内核park切换开销;自旋失败且前驱为SIGNAL时才park阻塞,兼顾高并发吞吐量与CPU利用率,既避免频繁内核切换,又杜绝无限自旋空转。
Q:为什么AQS不主动删除失效CANCELLED节点?
A:主动删除双向链表节点需要加锁,破坏无锁并发性能;采用惰性清理,后续线程自旋时顺带清理失效节点,无额外开销、并发安全、性能最优。
Q:PROPAGATE状态解决了什么核心问题?
A:修复JDK1.6之前共享锁高并发下的唤醒丢失BUG,保证共享资源释放后可持续向后传播唤醒,避免排队共享线程永久假死阻塞。
Q:AQS双向队列对比单向队列的核心优势?
A:支持反向遍历清理失效节点、精准校验前驱唤醒状态、解决并发入队指针延迟问题,保障高并发队列调度的准确性与安全性。
Q:park/unpark相较于wait/notify的核心优势?
A:无需锁依赖、支持精准唤醒、兼容先唤醒后阻塞的时序、支持限时阻塞,是AQS实现灵活可定制锁机制的底层基础。
九、AQS 与 Synchronized 底层全方位对比(满分必背·面试终极完整版)
synchronized 是 Java 原生内置重量级锁,依赖 JVM 底层实现;AQS 是 JDK1.5 提供的代码层同步框架,是 Lock 系列锁的底层基石,二者是 Java 并发同步的两大核心体系。本章摒弃简单罗列,从底层原理、锁升级、功能差异、性能取舍、适用场景做全网最全对比,覆盖所有深浅层面试追问,可直接全文背诵。
9.1 核心本质与实现层级差异(底层根源)
-
synchronized:JVM 底层 C++ 实现、原生语法级锁,属于 JVM 黑盒机制,无需手动编码,通过 JVM 自主完成锁竞争、线程排队、阻塞唤醒,开发者无法干预底层逻辑,无拓展性。
-
AQS(AbstractQueuedSynchronizer):Java 纯代码层实现的同步模板框架,属于白盒可定制机制,所有锁逻辑、排队逻辑、唤醒逻辑均可通过子类重写自定义,拓展性极强,是 Lock、Semaphore、CountDownLatch 等工具的底层核心。
9.2 锁机制与底层调度差异
9.2.1 线程调度方式
-
synchronized:依赖 JVM 内置的 Monitor 监视器锁,底层基于 OS 内核互斥量 实现线程阻塞与唤醒,线程竞争失败直接进入内核态阻塞,无自旋优化(JDK1.6 后引入偏向锁/轻量级锁优化)。
-
AQS:采用 用户态 CAS 自旋 + 内核态 park 阻塞 双层调度,优先用户态无锁抢锁,竞争激烈才进入队列阻塞,大幅减少内核态切换开销,调度更灵活、性能可控。
9.2.2 队列排队机制
-
synchronized:仅维护单一等待队列,所有阻塞线程统一排队,唤醒只能随机唤醒单个线程或批量唤醒所有线程,存在严重惊群效应,无法精准调度。
-
AQS:维护双向CLH同步队列 + 多条件队列,支持独占/共享双模式、多条件精准唤醒,可精准控制单线程/批量线程唤醒,彻底规避惊群问题。
9.3 核心功能特性全方位对比(面试高频)
该板块为面试核心考点,AQS 衍生的 Lock 锁功能全面碾压 synchronized,也是高并发场景优先使用 AQS 锁的核心原因。
|
对比维度 |
synchronized |
AQS(Lock系列) |
|
锁模式 |
仅支持独占排他模式,无共享锁能力 |
原生支持独占+共享双模式,适配读写锁、信号量等场景 |
|
锁公平性 |
默认非公平,无法手动开启公平锁 |
支持公平/非公平锁自由切换,按需配置 |
|
中断响应 |
不可中断,线程阻塞后永久死等,无法终止 |
支持可中断阻塞,线程可主动退出排队,避免卡死 |
|
超时机制 |
不支持超时抢锁,无时间限制阻塞 |
支持限时超时抢锁,超时自动放弃排队退出 |
|
精准唤醒 |
只能随机唤醒/全员唤醒,无精准调度 |
支持多Condition条件队列,精准唤醒指定线程 |
|
锁可重入 |
支持隐式可重入,无需手动计数 |
支持显式可重入,state精准计数,逻辑可控 |
|
读写分离 |
不支持读写分离,读写互斥,并发读性能极差 |
支持读写锁分离,读共享、写独占,适配读多写少场景 |
|
锁状态监控 |
无状态查询能力,无法获取锁占用、排队线程数 |
提供丰富API,可查询锁状态、排队线程、重入次数 |
|
拓展性 |
功能固定、黑盒不可修改、无拓展性 |
模板化可定制,可自定义各类同步工具 |
9.4 锁升级机制差异(JDK1.6核心优化)
9.4.1 synchronized 锁升级机制
synchronized 为优化低并发性能,内置偏向锁→轻量级锁→重量级锁逐级升级机制,锁只能升级、不能降级:低并发无竞争时用偏向锁/轻量级锁(CAS自旋),高并发竞争激烈时升级为重量级锁(内核阻塞),低并发性能优异,但高并发竞争下性能骤降。
9.4.2 AQS 锁机制
无锁升级概念,全程统一采用「CAS自旋尝试 + 队列阻塞兜底」机制,不依赖JVM锁升级优化,性能稳定可控:无论高低并发,性能波动极小,高并发场景性能碾压synchronized。
9.5 性能场景取舍(面试必背核心结论)
9.5.1 低并发场景
synchronized 性能更优:得益于JVM内置偏向锁、轻量级锁优化,无对象创建、无代码层调度开销,极简高效。
9.5.2 高并发竞争场景
AQS 性能碾压:AQS 自适应自旋、队列有序排队、精准唤醒、无无效竞争,规避内核频繁切换开销;synchronized 高并发下直接升级为重量级锁,线程大量阻塞、CPU开销极高。
9.6 代码使用便捷性差异
-
synchronized:语法简洁、自动加锁解锁、无需手动控制、无死锁风险(JVM自动释放锁)、底层无需开发者关注。
-
AQS(Lock):需手动 lock() 加锁、unlock() 解锁,必须搭配 try-finally 使用,编码复杂度更高,手动操作不当易引发死锁,但灵活性拉满。
9.7 适用业务场景精准区分
9.7.1 优先使用 synchronized 场景
低并发、简单同步逻辑、代码简洁优先、无需特殊锁特性(中断/超时/公平锁)、短周期同步代码块。
9.7.2 优先使用 AQS 锁(Lock)场景
高并发场景、读多写少场景、需要公平锁机制、需要超时/可中断锁、需要精准线程唤醒、需要监控锁状态、自定义同步逻辑的复杂业务。
9.8 本节终极面试绝杀总结(直接背诵满分话术)
A:synchronized 是JVM底层C++实现的原生语法锁,黑盒无拓展,仅支持独占模式、不可中断、无超时机制、唤醒粗糙,低并发依托锁升级机制性能优异,使用简单自动释锁;AQS是Java代码层实现的同步模板框架,是Lock系列底层核心,采用CAS自旋+队列阻塞双机制,原生支持独占/共享、公平/非公平、可中断、超时阻塞、精准条件唤醒,功能全面可定制,高并发性能稳定优越。简单来说:简单低并发场景用synchronized,高并发、复杂同步、可定制场景必用AQS锁。
十、AQS 高频面试绝杀问答(可直接背诵·全覆盖增补完整版)
Q1:AQS 核心原理是什么?
A:通过 state 状态变量标记资源占用状态,依托双向FIFO CLH同步队列管理阻塞排队线程,基于CAS无锁自旋抢占+LockSupport.park/unpark挂起唤醒机制,原生支持独占、共享两种同步模式,通过模板方法模式统一封装底层同步逻辑,是JUC所有锁和并发工具的底层核心基石。
Q2:AQS 为什么需要双向队列而非单向队列?
A:一是支持反向遍历,精准清理CANCELLED失效节点,适配惰性清理机制;二是可快速获取前驱节点,校验SIGNAL唤醒状态,构建可靠唤醒链路;三是解决高并发入队next指针赋值延迟问题,避免正向遍历漏判有效节点,保证队列调度精准安全。
Q3:独占锁和共享锁核心区别?
A:独占锁为排他模式,同一时刻仅单个线程持有资源,解锁仅一对一唤醒单个后继节点,无传播机制,适配互斥场景;共享锁为并发模式,多线程可同时持有资源,解锁后支持接力传播唤醒,批量放行排队线程,适配读并发、限流、倒计时场景,且拥有专属PROPAGATE传播状态。
Q4:Condition 和 Object.wait/notify 核心区别?
A:Condition支持多条件独立队列,可精准唤醒指定业务线程,无惊群效应;原生wait/notify仅单队列,只能随机唤醒或全员唤醒,无效竞争多。同时Condition原生支持超时、可中断、不可中断多种等待模式,功能全面,彻底弥补原生等待唤醒机制的短板。
Q5:非公平锁默认开启、性能更高的核心原因?
A:非公平锁允许新入场线程直接CAS插队抢锁,无需入队阻塞,大幅减少线程park/unpark内核切换开销;同时新线程CPU上下文、缓存更活跃,可直接执行业务逻辑,降低队列流转开销,极大提升系统吞吐量,仅存在轻微线程饥饿风险。
Q6:AQS 有限自旋为什么不会导致CPU 100%?
A:AQS并非无限自旋,属于自适应有限自旋。线程入队后仅短暂自旋重试抢锁,一旦判断锁长期被占用、且前驱节点为SIGNAL状态,会立即执行park挂起,释放CPU资源,彻底避免空转,兼顾性能与CPU利用率。
Q7:AQS 线程挂起的唯一安全条件是什么?为什么?
A:唯一条件是前驱节点waitStatus为SIGNAL(-1)。因为该状态代表前驱节点释放资源后一定会唤醒后继线程,能保证当前线程park后不会永久阻塞;若前驱为其他状态,无唤醒保障,直接挂起会导致线程假死、死锁。
Q8:AQS 失效节点为什么采用惰性清理,不主动删除?
A:高并发下主动删除双向链表节点需要加锁,会破坏AQS无锁并发的高性能架构;惰性清理由后续排队线程自旋时顺带完成,无需额外开销、无并发安全问题,且失效节点不影响核心抢锁、唤醒流程,性能最优。
Q9:PROPAGATE(-3) 状态的作用和诞生背景?
A:JDK1.6为修复共享锁高并发唤醒丢失BUG新增的专属状态。高并发多线程同时释放共享资源时,普通状态会导致后续排队线程无唤醒源而永久假死,PROPAGATE可标记节点具备持续传播唤醒能力,只要资源充足就接力批量唤醒后续共享线程,彻底解决唤醒丢失问题。
Q10:普通acquire锁和可中断锁的核心差异?
A:普通acquire阻塞过程中被中断,仅标记中断状态,不抛异常、不退出,继续排队死等锁资源;可中断acquireInterruptibly锁,检测到中断信号会立即抛异常、终止排队、退出阻塞,可有效避免线程永久积压卡死。
Q11:AQS超时锁短时间不park的优化原理?
A:park/unpark是内核态系统调用,开销极高。若剩余超时时间极短(小于1000纳秒),用户态自旋重试的开销远小于内核切换开销,因此AQS自适应放弃阻塞、短时自旋,最大化提升短时限时抢锁的性能。
Q12:Condition.await 为什么必须用 while 循环判断条件?
A:规避系统虚假唤醒问题。线程可能无理由被系统唤醒,此时业务条件并未满足,if单次判断会直接执行业务导致数据异常;while循环可反复校验条件,仅条件真正满足时才退出等待,保证业务安全。
Q13:await 方法执行后锁的状态是什么?为什么?
A:await会彻底释放全部锁资源,将state置0,包含所有可重入锁。如果不释放锁,其他线程无法获取锁、无法执行signal唤醒逻辑,会直接造成双向死锁,这是Condition等待的核心前置要求。
Q14:公平锁和非公平锁的唯一底层差异?
A:差异仅存在于tryAcquire快速抢锁阶段。非公平锁新线程可直接CAS插队抢锁;公平锁会先执行hasQueuedPredecessors校验队列,有排队线程则主动入队、禁止插队。队列自旋、挂起、唤醒、出队逻辑完全一致。
Q15:非公平锁队列是有序的吗?
A:完全有序。非公平锁仅允许新入场线程插队,一旦线程插队失败进入同步队列,严格遵循FIFO先进先出规则,有序自旋、有序唤醒,不会出现队列乱序竞争的情况。
Q16:AQS 可重入的实现原理,为什么不会死锁?
A:AQS本身不实现可重入,由ReentrantLock子类自定义实现:同一线程重复加锁时,校验当前锁归属为自身,仅对state自增计数,不入队、不阻塞;解锁时逐层递减,仅state归0才彻底释放锁。全程无排队阻塞逻辑,彻底规避自死锁。
Q17:LockSupport.park/unpark 对比 wait/notify 的核心优势?
A:一是无需依赖锁对象,无强制锁依赖;二是支持精准唤醒指定线程,无惊群效应;三是兼容先unpark唤醒、后park阻塞的时序,不会永久阻塞;四是支持限时阻塞,适配AQS超时锁机制,扩展性更强。
Q18:AQS 独占和共享模式为什么不能混用?
A:两种模式的唤醒机制、状态流转、队列逻辑完全不兼容。独占是一对一精准唤醒,依赖SIGNAL链路;共享是批量传播唤醒,依赖PROPAGATE状态。混用会导致资源计数错乱、唤醒逻辑失效、线程永久阻塞,因此一个同步器仅能选择一种模式。
Q19:synchronized 和 AQS 锁的核心性能取舍?
A:低并发场景下synchronized依托JVM偏向锁、轻量级锁优化,性能更优、使用简单;高并发竞争场景下,AQS依托自适应自旋、有序排队、精准唤醒,规避大量内核切换开销,性能和稳定性全面碾压synchronized。
Q20:AQS 整体核心设计思想是什么?
A:核心是CAS无锁快速抢占 + CLH队列兜底阻塞的双层架构。优先用户态自旋抢锁保证高性能,竞争失败后入队park阻塞杜绝CPU空转,兼顾高并发吞吐量与并发安全,是乐观抢占、悲观排队的经典并发设计。
Q21:hasQueuedPredecessors 方法的作用?
A:公平锁核心校验方法,用于判断同步队列是否存在有效前驱排队线程。队列有真实等待线程则返回true,禁止当前线程抢锁、必须入队;队列为空或仅存哨兵节点则返回false,允许直接抢锁,保障锁竞争时序公平。
Q22:AQS 同步队列和条件队列的核心流转规则?
A:双队列完全独立、节点互斥,线程同一时间只存在于一个队列。线程持有锁时在同步队列,调用await后脱离同步队列、进入条件队列阻塞;被signal唤醒后,从条件队列转移至同步队列重新排队抢锁,抢到锁后才真正唤醒执行业务。
Q23:为什么共享锁抢到锁后需要传播唤醒?
A:共享资源支持多线程并发占用,单次资源释放可能剩余多个可用配额。仅唤醒单个线程会造成资源闲置浪费,通过传播唤醒可批量放行后续排队共享线程,最大化利用系统资源、提升并发吞吐量。
Q24:AQS 无锁入队如何保证并发安全?
A:依靠CAS自旋+哨兵节点机制保障安全。新节点优先CAS挂载队尾,并发失败则进入死循环自旋重试;空队列自动初始化哨兵头节点,规避空指针问题,全程无锁、纯用户态操作,保证高并发入队百分百成功。
Q25:公平锁为什么无法彻底杜绝CPU饥饿?
A:公平锁仅保证锁竞争时序公平(FIFO排队),杜绝锁饥饿;但操作系统线程调度存在优先级、时间片、CPU亲和性等机制,部分线程获取CPU执行时间片更少,因此无法彻底杜绝底层CPU调度饥饿。
&spm=1001.2101.3001.5002&articleId=162940114&d=1&t=3&u=e0074025ca704074b3e07c5f8bbd5ad4)
528

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



