当前位置: 首页 > news >正文

AQS 与 ReentrantLock:队列同步器与可重入锁

如果说synchronized是 JVM 给你的内置锁,那 AQS 就是 JUC 里很多同步工具的地基。

ReentrantLockSemaphoreCountDownLatch这些类看起来用法不同,但底层都有一套相似的骨架:一个 state 表示资源状态,一条 FIFO 队列保存等待线程,再用 CAS 保证抢资源时的原子性。

这套骨架就是 AQS。

AQS 是什么

AQS,全称AbstractQueuedSynchronizer,抽象队列同步器。

它不是一个直接拿来用的业务工具,而是用来构建锁和同步组件的基础框架。

常见基于 AQS 的组件:

组件用途
ReentrantLock可重入互斥锁
Semaphore信号量,控制并发访问数量
CountDownLatch倒计时锁,等待多个任务完成
ReentrantReadWriteLock读写锁

AQS 的核心结构

AQS 最核心的东西有两个:

  1. volatile int state
  2. 一个 FIFO 双向队列。

state表示同步状态。不同工具对它的解释不同。

ReentrantLock里,state = 0通常表示没有线程持锁,state > 0表示锁被持有,并且数值可以表示重入次数。

Semaphore里,state可以表示剩余许可数。

CountDownLatch里,state可以表示还没倒完的计数。

AQS

volatile int state

FIFO 双向等待队列

head

Node: Thread-1

Node: Thread-2

tail

CAS 修改资源状态

这就是 AQS 的漂亮之处:它把“抢资源、失败排队、唤醒后继”这些通用流程抽出来,让不同同步工具只需要定义怎么获取和释放资源。

抢锁过程怎么走

以互斥锁为例,线程来抢资源时大概这么走:

线程尝试获取资源

state 是否可获取

CAS 修改 state

CAS 是否成功

获取成功

封装成 Node 入队

在 AQS 队列中等待

前驱释放资源后被唤醒

这里的关键点是 CAS。

多个线程同时看到state = 0时,只有一个线程能 CAS 成功。其他线程 CAS 失败后,就会进入队列等待。

AQS 公平还是非公平

AQS 本身既能支持公平,也能支持非公平。具体公平不公平,看上层同步器怎么实现。

公平锁的思路是:新来的线程先看队列里有没有人排队。如果有人排队,就不要插队,去队尾等。

非公平锁的思路是:新来的线程可以先尝试抢一下。抢到了就直接执行,抢不到再排队。

没有

新线程到来

公平锁?

队列里是否有等待线程

进入队列排队

尝试 CAS 抢锁

抢锁成功?

执行临界区

非公平锁吞吐量通常更高,因为减少了严格排队带来的调度成本。但它可能让队列里的老线程等得更久。

ReentrantLock 有哪些能力

ReentrantLock是 JUC 里的可重入锁。它和synchronized一样能互斥、能重入,但提供了更多控制能力:

能力synchronizedReentrantLock
自动释放锁否,需要unlock()
可重入
公平锁不支持显式选择构造器可选择
可中断等待不方便lockInterruptibly()
超时获取锁不支持tryLock(timeout, unit)
多条件队列一个 WaitSet多个Condition

基本用法一定要写成try finally

ReentrantLocklock=newReentrantLock();lock.lock();try{// 临界区}finally{lock.unlock();}

ReentrantLock不会像synchronized那样自动释放锁。如果忘了unlock(),后面的线程可能永远等下去。

公平锁和非公平锁

ReentrantLock默认是非公平锁:

ReentrantLocklock=newReentrantLock();

也可以传true创建公平锁:

ReentrantLockfairLock=newReentrantLock(true);

内部大致是选择不同的 Sync 实现:

publicReentrantLock(){sync=newNonfairSync();}publicReentrantLock(booleanfair){sync=fair?newFairSync():newNonfairSync();}

非公平锁不是“乱来”,它只是允许新线程先尝试 CAS 抢锁。如果失败,还是会进入 AQS 队列。

可中断和可超时

synchronized的一个限制是,线程如果阻塞在获取锁上,不太方便被取消。

ReentrantLock可以用lockInterruptibly()

try{lock.lockInterruptibly();}catch(InterruptedExceptione){Thread.currentThread().interrupt();return;}try{// 临界区}finally{lock.unlock();}

也可以用tryLock()设置超时时间:

if(!lock.tryLock(2,TimeUnit.SECONDS)){System.out.println("获取锁失败");return;}try{// 临界区}finally{lock.unlock();}

这在业务系统里很有用。比如一个操作拿不到锁就快速失败,不要一直把请求线程挂死。

多条件变量

synchronized每个对象只有一个 WaitSet,调用notify()notifyAll()时很难精准唤醒某一类等待线程。

ReentrantLock可以创建多个Condition

staticReentrantLocklock=newReentrantLock();staticConditionc1=lock.newCondition();staticConditionc2=lock.newCondition();

不同条件的线程进入不同等待队列,需要时可以精准唤醒。

ReentrantLock

AQS 同步队列

Condition c1 等待队列

Condition c2 等待队列

c1.signal / signalAll

c2.signal / signalAll

注意,await()signal()这些方法也必须在持有锁的情况下调用。

synchronized 和 Lock 怎么比较

可以从三个层面说。

语法层面:

synchronized是关键字,JVM 层面实现,退出同步代码块自动释放锁。Lock是接口,JDK 层面实现,需要手动释放锁。

功能层面:

二者都能互斥、同步、重入。Lock额外支持公平锁、可中断、可超时、多条件变量等能力。

性能层面:

早期synchronized性能较弱,但后来有偏向锁、轻量级锁等优化,低竞争场景并不差。竞争复杂、需要更多控制能力时,ReentrantLock更灵活。

面试怎么答

可以这样讲:

AQS 是 JUC 里构建锁和同步工具的基础框架,核心是一个volatile int state和一条 FIFO 双向队列。线程获取资源时会先用 CAS 修改 state,成功就获取资源,失败就封装成 Node 进入队列等待。

ReentrantLock底层就是基于 AQS 实现的。它默认是非公平锁,也可以通过构造器创建公平锁。非公平锁允许新来的线程先 CAS 抢一下锁,公平锁会先判断队列里是否已有等待线程。

相比synchronizedReentrantLock支持可中断、可超时、公平锁、多条件变量,但必须在finally里手动unlock()。如果只是简单互斥,synchronized更简洁;如果需要更强控制能力,用ReentrantLock更合适。

http://www.jsqmd.com/news/946394/

相关文章:

  • 别再手动删Flink Checkpoint了!RocksDB增量模式下,教你正确配置state.checkpoints.num-retained
  • Simulink模型生成DLL时,你八成会踩的这几个坑(附R2017a/b与VS版本匹配避坑指南)
  • 实战演练:在快马云端环境从零开发一个java任务管理应用
  • 深度探索OpenCore Legacy Patcher:技术揭秘老Mac的非官方升级方案
  • 告别重复造轮子:用快马AI一键生成I2C扫描与软件定时器模块,提升嵌入式开发效率
  • 2026年比较好的电加热导热油锅炉/江苏电加热炉多家厂家对比分析 - 行业平台推荐
  • 【紧急预警】传统预测模型已失效!2024Q2起,未整合LLM增强推理的预测系统将面临监管穿透式审查
  • 别再用Excel做战略推演了!2024智能决策黄金三角模型:因果推理×实时知识图谱×人机协同校验
  • UNet 模型结构从零搭建与实战解析
  • 【实战指南】4大场景玩转WzComparerR2:解密冒险岛WZ文件的终极方案
  • 从SolidWorks到WebGL:一个完整的三维模型‘搬家’流程与踩坑实录
  • 小米AI团队揭秘:MiMo-V2-Flash-Base的27T tokens训练工程实践
  • Mermaid实时编辑器架构设计:企业级图表协作与可视化开发平台
  • 2026年海宁市空调维修避坑指南:5家靠谱专业推荐 - 本地品牌推荐
  • Dify工作流实战指南:从零构建企业级AI应用
  • 北斗模块的NMEA语句和GPS的有什么不同?手把手教你识别$BD、$GP和$GN开头的定位数据
  • QGIS制图进阶:除了‘四色’,如何用【拓扑着色】的‘颜色平衡’选项做出更美观的地图?
  • 终极指南:用Oemer光学音乐识别系统轻松将乐谱照片转为数字音乐
  • 别再只做词频统计了!用jieba自定义词典挖掘年报中的‘专业度’与‘模糊性’
  • 别再乱用detach()了!用C++11/14/17实战案例解析线程生命周期管理的正确姿势
  • 【Clickhouse从入门到精通】第56篇:ClickHouse运维常见问题与故障排查指南
  • SukiUI完整指南:5分钟打造专业级Avalonia桌面应用界面
  • TimeMoE-200M未来展望:从2亿参数到更大规模模型的演进路线
  • 别再让CPU干杂活了!手把手教你用STM32的DMA给串口发送数据提速
  • 如何用Paperless-ngx打造你的数字文档管理中枢:从零开始构建智能归档系统
  • AIOps落地失败率高达73%?揭秘头部企业私有化整合框架(2024最新Gartner认证实践)
  • 告别CLI手忙脚乱:用Docker+OpenConfig+gRPC,5分钟搞定网络设备数据采集
  • redis-数据安全性
  • AutoJs Pro 7.0.4-1 避坑指南:一机一号稳定运行快手极速版,告别封号风险
  • 别再混淆了!深入对比SO_REUSEADDR和SO_REUSEPORT:在Linux下实现UDP/TCP多进程监听同一端口