AQS原理+ReentrantLock源码+与synchronized深度对比

news2026/4/26 13:13:36
并发编程是Java高级开发的核心门槛而AQS、ReentrantLock、synchronized则是并发领域的“铁三角”。很多开发者只会用ReentrantLock和synchronized做同步却不懂其底层依赖的AQS框架面试时被问“ReentrantLock和synchronized的区别”“AQS原理是什么”往往只能答表面无法触及核心。一、前置认知为什么要懂AQSAQSAbstractQueuedSynchronizer即抽象队列同步器是Java并发包java.util.concurrent.locks的核心基石——ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch等并发工具底层都依赖AQS实现。简单来说AQS就是一个“模板框架”它定义了一套并发同步的规范封装了“线程排队、锁竞争、唤醒机制”等通用逻辑开发者只需重写少量方法就能快速实现自定义的同步锁或同步器无需重复编写复杂的排队和唤醒逻辑。核心价值解耦“通用并发逻辑”与“具体锁实现”让并发工具的开发更高效、更规范。二、AQS核心原理AQS的核心设计可以概括为一个状态变量 一个双向阻塞队列 一套模板方法三者协同实现线程的同步与竞争。1. 核心组件1状态变量stateAQS用一个volatile修饰的int变量state来表示“同步状态”其含义由具体的实现类如ReentrantLock定义核心作用是标记锁的占用情况和重入次数state 0表示锁处于空闲状态无线程占用state 0表示锁已被占用state的值代表“重入次数”如state2说明当前线程重入了2次state 0无效状态通常不会出现。AQS提供了3个核心方法用于操作state保证原子性和可见性// 获取当前同步状态volatile保证可见性protectedfinalintgetState(){returnstate;}// 设置同步状态仅在当前线程持有锁时使用无并发竞争protectedfinalvoidsetState(intnewState){statenewState;}// CAS原子更新同步状态核心用于锁竞争时的状态修改protectedfinalbooleancompareAndSetState(intexpect,intupdate){returnunsafe.compareAndSwapInt(this,stateOffset,expect,update);}注unsafe是Java底层的Unsafe类提供了CAS原子操作保证state的修改是线程安全的stateOffset是state变量在AQS类中的内存偏移量用于Unsafe直接操作内存。2. 核心组件2双向阻塞队列CLH队列当多个线程竞争锁失败时AQS会将这些线程封装成“节点Node”加入到一个双向虚拟队列中CLH队列变体让线程阻塞等待当锁释放时再从队列中唤醒一个线程竞争锁。CLH队列的核心特点虚拟队列队列本身没有实际的容器而是通过节点的prev前驱和next后继指针形成双向链表结构节点状态每个Node都有一个waitStatus变量标记节点的状态如等待中、已取消、已唤醒用于控制线程的阻塞和唤醒FIFO原则队列遵循“先进先出”保证线程竞争锁的顺序公平锁依赖此特性非公平锁可打破此顺序。Node节点核心结构staticfinalclassNode{// 标记节点类型独占锁ReentrantLock用此类型、共享锁Semaphore用此类型staticfinalNodeEXCLUSIVEnull;staticfinalNodeSHAREDnewNode();// 节点状态CANCELLED1已取消、SIGNAL-1等待唤醒、CONDITION-2条件等待等volatileintwaitStatus;// 前驱节点volatileNodeprev;// 后继节点volatileNodenext;// 当前节点对应的线程volatileThreadthread;// 条件队列相关ReentrantLock的Condition依赖NodenextWaiter;}队列工作流程线程竞争锁失败 → 封装成Node节点通过CAS操作将节点加入队列尾部当前线程调用LockSupport.park()方法进入阻塞状态持有锁的线程释放锁时唤醒队列头部的节点该节点对应的线程重新竞争锁。3. 核心组件3模板方法模式AQS的核心设计模式是模板方法模式它定义了一套“锁获取、锁释放”的通用流程模板方法将具体的“锁竞争、锁释放”逻辑交给子类如ReentrantLock的Sync内部类重写。AQS的核心模板方法开发者无需重写直接调用acquire(int arg)独占式获取锁ReentrantLock的lock()方法底层调用此方法release(int arg)独占式释放锁ReentrantLock的unlock()方法底层调用此方法acquireShared(int arg)共享式获取锁Semaphore的acquire()方法底层调用releaseShared(int arg)共享式释放锁Semaphore的release()方法底层调用。AQS要求子类重写的核心方法仅需重写这几个其余逻辑由AQS封装tryAcquire(int arg)独占式尝试获取锁ReentrantLock的公平/非公平锁核心实现tryRelease(int arg)独占式尝试释放锁tryAcquireShared(int arg)共享式尝试获取锁tryReleaseShared(int arg)共享式尝试释放锁isHeldExclusively()判断当前线程是否独占持有锁用于可重入性判断。模板方法流程以acquire()为例// AQS的acquire方法模板方法定义了独占式获取锁的完整流程publicfinalvoidacquire(intarg){// 1. 尝试获取锁子类重写tryAcquire获取成功则直接返回if(!tryAcquire(arg)// 2. 尝试获取锁失败将当前线程封装成Node加入队列acquireQueued(addWaiter(Node.EXCLUSIVE),arg)){// 3. 如果线程在队列中被中断标记当前线程为中断状态selfInterrupt();}}总结AQS核心用state控制锁的状态用CLH队列管理等待线程用模板方法封装通用流程子类重写具体的锁逻辑——这就是ReentrantLock能够实现公平锁、非公平锁、可重入性的底层基础。三、ReentrantLock源码解析基于AQS的实现ReentrantLock可重入锁是AQS最经典的实现类核心特性可重入、支持公平锁和非公平锁默认非公平锁、支持中断、支持条件等待Condition。ReentrantLock的核心结构内部维护了一个Sync抽象内部类继承AQSSync有两个子类——NonfairSync非公平锁和FairSync公平锁ReentrantLock的所有锁操作最终都委托给Sync子类实现。1. ReentrantLock构造方法公平/非公平锁选择// 无参构造默认非公平锁性能更优大多数场景首选publicReentrantLock(){syncnewNonfairSync();}// 有参构造true公平锁false非公平锁publicReentrantLock(booleanfair){syncfair?newFairSync():newNonfairSync();}公平锁与非公平锁的核心区别公平锁严格遵循“先到先得”CLH队列顺序非公平锁允许“插队”新线程可直接竞争锁无需排队。非公平锁性能更优因为减少了线程排队、唤醒的开销公平锁则保证了线程竞争的公平性避免线程饥饿。2. 核心方法lock()获取锁ReentrantLock的lock()方法本质是调用Sync子类的lock()方法再间接调用AQS的acquire(1)方法arg1表示每次获取锁state1。1非公平锁NonfairSynclock()staticfinalclassNonfairSyncextendsSync{privatestaticfinallongserialVersionUID7316153563782823691L;// 非公平锁获取锁的核心方法finalvoidlock(){// 1. 尝试CAS抢锁state从0→1如果成功标记当前线程为锁持有者if(compareAndSetState(0,1))setExclusiveOwnerThread(Thread.currentThread());else// 2. 抢锁失败调用AQS的acquire(1)进入队列等待acquire(1);}// 重写AQS的tryAcquire方法非公平锁的锁竞争逻辑protectedfinalbooleantryAcquire(intacquires){returnnonfairTryAcquire(acquires);}}核心逻辑非公平锁的“插队”特性体现在第一步——即使队列中有等待的线程新线程也会先尝试CAS抢锁抢锁成功则直接持有锁无需排队只有抢锁失败才会加入队列。2nonfairTryAcquire()非公平锁尝试获取锁// 非公平锁尝试获取锁的核心逻辑可重入性实现finalbooleannonfairTryAcquire(intacquires){finalThreadcurrentThread.currentThread();intcgetState();// 1. 如果锁空闲state0直接CAS抢锁if(c0){if(compareAndSetState(0,acquires)){setExclusiveOwnerThread(current);returntrue;}}// 2. 如果当前线程就是锁持有者可重入性state1elseif(currentgetExclusiveOwnerThread()){intnextccacquires;if(nextc0)// 防止重入次数溢出thrownewError(Maximum lock count exceeded);setState(nextc);// 无需CAS因为当前线程已持有锁无并发竞争returntrue;}// 3. 锁被其他线程持有抢锁失败returnfalse;}3公平锁FairSynclock()staticfinalclassFairSyncextendsSync{privatestaticfinallongserialVersionUID-3000897897090466540L;finalvoidlock(){// 公平锁不“插队”直接调用AQS的acquire(1)进入队列排队acquire(1);}// 重写AQS的tryAcquire方法公平锁的锁竞争逻辑protectedfinalbooleantryAcquire(intacquires){finalThreadcurrentThread.currentThread();intcgetState();if(c0){// 关键区别公平锁抢锁前先判断队列中是否有等待的线程// hasQueuedPredecessors()判断当前线程是否是队列的第一个节点if(!hasQueuedPredecessors()compareAndSetState(0,acquires)){setExclusiveOwnerThread(current);returntrue;}}// 可重入性逻辑与非公平锁一致elseif(currentgetExclusiveOwnerThread()){intnextccacquires;if(nextc0)thrownewError(Maximum lock count exceeded);setState(nextc);returntrue;}returnfalse;}}核心区别公平锁在抢锁前会通过hasQueuedPredecessors()方法判断“队列中是否有比当前线程更早等待的线程”如果有当前线程不会抢锁直接加入队列只有队列中没有等待线程才会尝试CAS抢锁——这就是公平锁“先到先得”的核心实现。3. 核心方法unlock()释放锁ReentrantLock的unlock()方法无论公平锁还是非公平锁逻辑一致都是调用Sync的release(1)方法再间接调用AQS的release(1)方法。// ReentrantLock的unlock方法publicvoidunlock(){sync.release(1);}// Sync类继承AQS的release方法protectedfinalbooleantryRelease(intreleases){// 1. 重入次数减1releases1intcgetState()-releases;// 2. 只有锁持有者才能释放锁否则抛出异常if(Thread.currentThread()!getExclusiveOwnerThread())thrownewIllegalMonitorStateException();booleanfreefalse;// 3. 如果重入次数为0说明锁已完全释放标记锁为空闲if(c0){freetrue;setExclusiveOwnerThread(null);// 清空锁持有者}// 4. 更新state即使未完全释放也要更新重入次数setState(c);returnfree;}核心逻辑可重入锁的释放是“渐进式”的——每次unlock()state减1只有当state减到0时才会真正释放锁清空锁持有者并唤醒队列中的等待线程如果state0说明线程还在重入锁未完全释放。注意ReentrantLock的unlock()必须手动调用且必须放在finally块中否则会导致锁泄漏线程异常退出锁未释放其他线程无法获取锁。4. 关键特性可重入性、中断、Condition可重入性通过tryAcquire()中“判断当前线程是否是锁持有者”实现state记录重入次数每次获取锁state1释放锁state-1state0时锁释放可中断ReentrantLock提供lockInterruptibly()方法允许线程在等待锁时被中断synchronized不支持中断Condition条件等待通过newCondition()方法创建Condition对象实现“等待-通知”机制支持多条件等待synchronized只能通过wait()/notify()实现单一条件等待。四、ReentrantLock与synchronized深度对比ReentrantLock和synchronized都是Java中实现线程同步的核心方式二者底层原理不同、特性不同适用场景也不同。1. 核心对比表对比维度ReentrantLocksynchronized底层实现基于AQS框架Java代码实现依赖CAS和CLH队列JVM内置锁C实现基于对象头的Monitor管程模型锁类型可重入锁支持公平锁、非公平锁默认非公平可重入锁仅支持非公平锁JDK1.8优化后性能接近ReentrantLock锁释放手动释放必须在finally块中调用unlock()否则锁泄漏自动释放线程退出同步块/方法时JVM自动释放锁无需手动操作中断支持支持中断lockInterruptibly()方法等待锁的线程可被中断不支持中断等待锁的线程会一直阻塞无法被中断条件等待支持多条件等待Condition可实现精准的线程唤醒仅支持单一条件等待wait()/notify()/notifyAll()唤醒时随机唤醒一个线程锁粒度细粒度可灵活控制锁的范围如代码块锁粗粒度早期JDK1.8后优化支持偏向锁、轻量级锁粒度可细化性能高并发场景下性能更优无锁竞争时略逊于synchronized有CAS开销JDK1.8前性能较差JDK1.8后偏向锁、轻量级锁性能接近ReentrantLock无锁竞争时性能更优公平性可选择公平/非公平锁公平锁保证线程排队顺序仅非公平锁无法保证线程竞争顺序可能出现线程饥饿使用复杂度较复杂需手动创建锁对象、手动释放注意异常处理简单只需加关键字方法/代码块无需手动管理锁的生命周期适用场景高并发、需要灵活控制锁中断、公平锁、多条件等待的场景低并发、简单同步场景或不需要灵活控制锁的场景优先选择开发效率高2. 关键差异补充1锁的升级机制不同synchronized在JDK1.8后引入了“锁升级”机制无锁 → 偏向锁 → 轻量级锁 → 重量级锁根据线程竞争情况动态升级锁的级别减少锁的开销而ReentrantLock没有锁升级机制始终基于AQS的CLH队列和CAS操作锁的级别固定独占锁。2异常处理不同synchronized遇到异常时JVM会自动释放锁不会导致锁泄漏而ReentrantLock如果在lock()后、unlock()前发生异常会导致锁无法释放锁泄漏因此必须将unlock()放在finally块中确保无论是否异常锁都能释放。3线程唤醒机制不同synchronized的notify()方法会随机唤醒一个等待的线程notifyAll()会唤醒所有等待的线程无法精准唤醒指定线程而ReentrantLock的Condition可以实现精准唤醒signal()唤醒一个指定条件的线程signalAll()唤醒所有指定条件的线程灵活性更高。3. 实战选择建议如果是简单的同步场景如单例模式、简单方法同步优先选择synchronized——开发效率高无需手动管理锁JDK优化后性能足够如果是高并发场景或需要灵活控制锁如公平锁、中断、多条件等待选择ReentrantLock——灵活性高性能更稳定如果是分布式场景无论是ReentrantLock还是synchronized都不适用仅支持单机同步需使用分布式锁如Redisson、ZooKeeper实现。五、总结核心要点梳理AQS核心一个state状态变量控制锁状态、一个CLH双向队列管理等待线程、一套模板方法封装通用流程是并发工具的底层基石ReentrantLock核心基于AQS实现内部有NonfairSync非公平锁和FairSync公平锁两个子类支持可重入、中断、多条件等待需手动释放锁与synchronized差异底层实现不同、锁类型不同、释放方式不同、灵活性不同选择时需结合场景简单场景用synchronized复杂场景用ReentrantLock重点AQS的核心组件、ReentrantLock的公平/非公平锁实现、可重入性原理、二者的核心差异及适用场景。懂AQS才能看懂Java并发工具的底层逻辑懂ReentrantLock与synchronized的差异才能在生产中选择最合适的同步方式。并发编程的核心不是“加锁”而是“合理控制线程竞争”——AQS提供了竞争的框架ReentrantLock和synchronized提供了具体的竞争实现掌握它们才能搞定高并发场景。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2552196.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…