Java学习笔记(多线程):ReentrantLock 源码分析
本文是自己的学习笔记,主要参考资料如下
JavaSE文档
- 1、AQS 概述
- 1.1、锁的原理
- 1.2、任务队列
- 1.2.1、结点的状态变化
- 1.3、加锁和解锁的简单流程
- 2、ReentrantLock
- 2.1、加锁源码分析
- 2.1.1、tryAcquire()的具体实现
- 2.1.2、acquirQueued()的具体实现
- 2.1.3、tryLock的具体实现
- 2.1.5、总结
1、AQS 概述
1.1、锁的原理
AQS是指抽象类AbstractQueuedSynchronizer。这个抽象类代表着一种实现并发的方式。
具体实现方式是使用volitile修饰state变量,保证了state的可见性和有序性。最后使用CAS改变state的值,保证原子性。
那么AbstractQueuedSynchronizer通过更新state的值来实现的加锁和解锁。
下面是关键源代码的截图。


1.2、任务队列
AQS中维护了一个任务队列,是一个双向队列。队列节点是内部类Node。
在Node中记录者节点的状态waitStatus,比如CANCEL,SIGNAL等分别表示该任务节点已经取消和任务节点正在沉睡需要被唤醒。
当然,因为是双向列表所以也有指向前后节点的指针。下面是Node源码的部分截图。

这个队列会初始化一个头结点和一个尾结点作为虚拟节点。头结点的状态在整个加锁和释放锁的过程中都会变化。
1.2.1、结点的状态变化
当头结点指向的Node才拥有锁。
这里主要介绍三个状态
0, 表示当前Node后续无节点在排队。不表明是否拥有锁。-1,表示除了当前Node在排队以外,还有其他Node排在当前Node后面。不表明是否拥有锁。1,表示当前Node可能因为等待时间太长而放弃获取锁。
下面是三个Node在队列中的状态。这里从左到右解释他们的状态。

head指向第一个Node,所以当前Node拥有锁。
第一个Node的waitStatus=-1表示后续有节点等待获取锁。当该节点释放锁时会唤醒后续的节点。
第二个Node的waitStatus = -1,后续有节点等待获取锁。
第三个Node的waitStatus = 0,后续无节点等待获取锁。
1.3、加锁和解锁的简单流程
假设有两个线程A和B,他们需要争夺基于AQS实现的锁,下面是争夺的简单流程。
- 线程A先执行CAS,将state从0修改为1,线程A就获取到了锁资源,去执行业务代码即可。
- 线程B再执行CAS,发现state已经是1了,无法获取到锁资源。
- 线程B需要去排队,将自己封装为Node对象。
- 需要将当前B线程的Node放到双向队列保存,排队。
2、ReentrantLock
2.1、加锁源码分析
ReentrantLock分为公平锁和非公平锁。在加锁的时候因这两种锁的不同会有不同的加锁方式。
ReentrantLock默认是非公平锁,构造方法中传入false则是公平锁。
非公平锁的lock()方法会直接基于CAS尝试获取锁,如果成功的话则执行setExclusiveOwnerThread()方法表示当前线程持有该锁;如果失败则执行acquire()方法。
公平锁则是直接执行acquire()方法。下面是源码对比。

接下来的重点则是看acquire()的具体操作。
tryAcquire()方法会再次尝试获取锁,如果成功返回true,否则返回false。
可以看到如果失败的话则将请求放到等待队列中同时发送中断信号。

2.1.1、tryAcquire()的具体实现
- 非公平锁
非公平锁会尝试再次直接通过CAS获取锁资源。因为是可重入锁,所以当锁的持有者是当前线程时也可直接获取锁,然后计数器加一。

- 公平锁
公平锁的逻辑与非公平锁类似,只不过再获取锁之前会先判断AQS中自己是不是排在第一位,之后才会获取锁。

2.1.2、acquirQueued()的具体实现

当tryAcquire()返回false,即获取锁失败,就开始尝试将当前线程封装成Node节点插入到AQS的结尾。
在插入时我们会看到if(p == head && tryAcquire(arg))这样的语句。
这是因为AQS有伪头结点,所以当这个线程插入到AQS中时发现自己的上一个节点是头结点,即自己排在第一位,那无论是公平锁还是非公平锁自己都可以再次测试获取锁。所以会再次执行tryAcquire()。
final boolean acquireQueued(final Node node, int arg) {// 不考虑中断// failed:获取锁资源是否失败(这里简单掌握落地,真正触发的,还是tryLock和lockInterruptibly)boolean failed = true;try {boolean interrupted = false;for (;;) {// 拿到当前节点的前继节点final Node p = node.predecessor();// 前继节点是否是head,如果是head,再次执行tryAcquire尝试获取锁资源。if (p == head && tryAcquire(arg)) {// 获取锁资源成功setHead(node);p.next = null; // 获取锁失败标识为falsefailed = false;return interrupted;}// 没拿到锁资源……// shouldParkAfterFailedAcquire:基于上一个节点转改来判断当前节点是否能够挂起线程,如果可以返回true,// 如果不能,就返回false,继续下次循环if (shouldParkAfterFailedAcquire(p, node) &&// 这里基于Unsafe类的park方法,将当前线程挂起parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)// 在lock方法中,基本不会执行。cancelAcquire(node);}
}
2.1.3、tryLock的具体实现
无参的tryLock()比较简单,和tryAcquire()基本没区别。
这里主要讲解有参的tryAcquireNanos(int arg, long nanosTimeout)。
它的作用在一个时间内尝试获得锁。在这个时间内没有获得锁会挂起park线程。如果成功则返回true,时间结束还没有获得则返回false。
public boolean tryLock(long timeout, TimeUnit unit)throws InterruptedException {return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
该方法需要处理中断异常,和lock()方法不一样。
我们继续深入。
public final boolean tryAcquireNanos(int arg, long nanosTimeout)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();return tryAcquire(arg) ||doAcquireNanos(arg, nanosTimeout);
}
可以看到,它直接通过线程的中断标志位决定是否抛出异常。
之后进行tryAcquire(),这个方法细节上面分析过,它有公平和非公平两种实现,简而言之就是非公平直接尝试CAS加锁,公平则是进入队列排队。
也就是说,最后它会正常加锁,只有失败时才会执行doAcquireNanos()。所以有参的tryLock()方法park线程的细节就在其中。
那下面就看看这个方法的内部。
核心就是线程会被封装Node放到队列中,之后查看时间,如果时间比较长,就park线程直到时间结束后再尝试获取锁;如果时间比较短,就在死循环中等到时间结束然后再次获得锁。
因为park的线程主要会因两个动作结束park,即时间到,或者线程发出中断状态,所以最后会查看park是因为什么结束的。如果是中断则抛出异常,否则尝试获取锁。
private boolean doAcquireNanos(int arg, long nanosTimeout)throws InterruptedException {// 如果等待时间是0秒,直接告辞,拿锁失败 if (nanosTimeout <= 0L)return false;// 设置结束时间。final long deadline = System.nanoTime() + nanosTimeout;// 先扔到AQS队列final Node node = addWaiter(Node.EXCLUSIVE);// 拿锁失败,默认trueboolean failed = true;try {for (;;) {// 如果在AQS中,当前node是head的next,直接抢锁final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return true;}// 结算剩余的可用时间nanosTimeout = deadline - System.nanoTime();// 判断是否是否用尽的位置if (nanosTimeout <= 0L)return false;// shouldParkAfterFailedAcquire:根据上一个节点来确定现在是否可以挂起线程if (shouldParkAfterFailedAcquire(p, node) &&// 避免剩余时间太少,如果剩余时间少就不用挂起线程nanosTimeout > spinForTimeoutThreshold)// 如果剩余时间足够,将线程挂起剩余时间LockSupport.parkNanos(this, nanosTimeout);// 如果线程醒了,查看是中断唤醒的,还是时间到了唤醒的。if (Thread.interrupted())// 是中断唤醒的!throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}
}
2.1.5、总结
ReentrantLock的加锁有公平锁和非公平锁两种方式。
对于非公平锁,任务一开始会直接尝试通过CAS获取锁,失败后才会进入任务队列。并且进入的时候会再次尝试获取锁。整个过程并不考虑其他节点等了多久,所以才是非公平锁。
对于公平锁,任务会按序先进入任务队列,直到有人唤醒他们才会开始获取锁。
相关文章:
Java学习笔记(多线程):ReentrantLock 源码分析
本文是自己的学习笔记,主要参考资料如下 JavaSE文档 1、AQS 概述1.1、锁的原理1.2、任务队列1.2.1、结点的状态变化 1.3、加锁和解锁的简单流程 2、ReentrantLock2.1、加锁源码分析2.1.1、tryAcquire()的具体实现2.1.2、acquirQueued()的具体实现2.1.3、tryLock的具…...
【LeetCode 热题100】二叉树构造题精讲:前序 + 中序建树 有序数组构造 BST(力扣105 / 108)(Go语言版)
🌱 二叉树构造题精讲:前序 中序建树 & 有序数组构造 BST 本文围绕二叉树的两类构造类题目展开解析: 从前序与中序遍历序列构造二叉树 将有序数组转换为二叉搜索树 我们将从「已知遍历构造树」和「平衡构造 BST」两个角度,拆…...
【软考系统架构设计师】系统配置与性能评价知识点
1、 常见的性能指标 主频外频*倍频 主频1/CPU时钟周期 CPI(Clock Per Instruction)平均每条指令的平均时间周期数 IPC(Instruction Per Clock)每时钟周期运行指令数 MIPS百万条指令每秒 MFLOPS百万个浮点操作每秒 字长影响运算的…...
【android bluetooth 协议分析 01】【HCI 层介绍 1】【hci_packets.pdl 介绍】
在 AOSP 的蓝牙协议栈 (Gabeldorsche) 中,hci_packets.pdl 是一个 协议描述语言文件,用于定义 HCI (Host Controller Interface) 层的数据包结构和通信协议。以下是详细解析: 1. 文件作用 system/gd/hci/hci_packets.pdl 协议自动化生成&…...
低资源需求的大模型训练项目---调研0.5B大语言模型
一、主流0.5B大语言模型及性能对比 1. Qwen系列(阿里) • Qwen2.5-0.5B:阿里2024年9月开源的通义千问系列最小尺寸模型,支持32K上下文长度和8K生成长度。在中文场景下表现优异,指令跟踪、JSON结构化输出能力突出&…...
Spring Boot 中集成 Disruptor_高性能事件处理框架
1. 引言 1.1 什么是 Disruptor Disruptor 是一个高性能的事件处理框架,广泛应用于金融交易系统、日志记录、消息队列等领域。它通过无锁机制和环形缓冲区(Ring Buffer)实现高效的事件处理,具有极低的延迟和高吞吐量的特点。 1.2 为什么使用 Disruptor 高性能:通过无锁机…...
解锁Midjourney创作潜能:超详细提示词(Prompts)分类指南
AI生图自由!就来 ChatTools (https://chat.chattools.cn),畅享Midjourney免费无限绘画。同时体验GPT-4o、Claude 3.7 Sonnet、DeepSeek等强大模型。 为了帮助大家更好地驾驭Midjourney,我们精心整理并分类了大量常用且效果出众的提示词。无论…...
Vue3.5 + Vite6.x 项目的完整 Stylelint 配置方案,支持 .vue/.html 内联样式、Less/SCSS/CSS 等多种文件类
Vue3.5 Vite6.x 项目的完整 Stylelint 配置方案,支持 .vue/.html 内联样式、Less/SCSS/CSS 等多种文件类型 一、完整依赖安装 npm install --save-dev stylelint stylelint-config-standard postcss-html # 解析 Vue/HTML 文件中的样式postcss-scss …...
大模型分布式推理和量化部署
一、小常识 1、计算大模型占用多少显存 对于一个7B(70亿)参数的模型,每个参数使用16位浮点数(等于 2个 Byte)表示,则模型的权重大小约为: 7010^9 parameters2 Bytes/parameter=14GB 70亿个参数每个参数占用2个字节=14GB 所以我们需要大于14GB的显存。注意14GB单纯是大…...
Ubuntu 下通过 Docker 部署 WordPress 服务器
最近想恢复写私人博客的习惯,准备搭建一个wordpress。 在这篇博客中,我将记录如何在 Ubuntu 环境下通过 Docker 部署一个 WordPress 服务器。WordPress 是一个流行的内容管理系统(CMS),它让用户能够轻松地创建和管理网…...
【ROS】分布式通信架构
【ROS】分布式通信架构 前言环境要求主机设置(Master)从机设置(Slave)主机与从机通信测试本文示例启动ROS智能车激光雷达节点本地计算机配置与订阅 前言 在使用 ROS 时,我们常常会遇到某些设备计算能力不足的情况。例…...
零基础HTML·笔记(持续更新…)
基础认知 HTML标签的结构 <strong>文字变粗</strong> <开始标签>内容<结束标签> 结构说明: 标签由<、>、1、英文单词或字母组成。并且把标签中<>包括起来的英文单词或字母称为标签名。常…...
Visual Studio 2022 UI机器学习训练模块
VS你还是太超标了,现在机器学习都不用写代码了吗!! 右键项目解决方案,选择机器学习模型...
Day5:关于MySQL的数据操作——插入数据
使用INSERT语句,其基本语法格式如下: INSERT [ IGNORE ] [ INTO ] 表名( 字段名称1 [ , 字段名称2 … ] ) VALUES ( { 表达式1 | DEFAULT } [ , { 表达式2 | DEFAULT } … ] ); IGNORE:当插入不符合数据完整性约束的数据时…...
FreeRTOS使任务处于阻塞态的API
在FreeRTOS中,任务进入阻塞状态通常是因为等待某个事件或资源。以下是常用的使任务进入阻塞态的API及其分类: 1. 任务延时 vTaskDelay(pdMS_TO_TICKS(ms)) 将任务阻塞固定时间(相对延时,从调用时开始计算)。 示例&…...
阿里云负载均衡可以抗ddos吗
本文深度解析阿里云负载均衡的DDoS防护机制,通过实测数据验证其基础防御能力边界,揭示需结合云盾高防IP实现TB级流量清洗的工程实践。结合2023年Memcached反射攻击事件,提供混合云架构下的多层级防御方案设计指南。 云原生负载均衡的基础防护…...
独立开发者之网站的robots.txt文件如何生成和添加
robots.txt是一个存放在网站根目录下的文本文件,用于告诉搜索引擎爬虫哪些页面可以抓取,哪些页面不可以抓取。下面我将详细介绍如何生成和添加robots.txt文件。 什么是robots.txt文件? robots.txt是遵循"机器人排除协议"(Robots…...
CentOS Stream release 9安装 MySQL(二)
在 CentOS Stream 9 上安装 MySQL 8.0 后,默认情况下 root 用户只能从本地(localhost)连接。如果你需要允许 root 用户远程访问,需要执行以下步骤: 1. 确保 MySQL 允许远程连接 (1) 检查 MySQL 监听地址 默认情况下,MySQL 8.0 可能只监听 127.0.0.1(本地回环地址),需…...
Leedcode刷题 | Day31_贪心算法05
一、学习任务 56. 合并区间代码随想录738. 单调递增的数字968. 监控二叉树 二、具体题目 1.56合并区间56. 合并区间 - 力扣(LeetCode) 给出一个区间的集合,请合并所有重叠的区间。 示例 1: 输入: intervals [[1,3],[2,6],[8,10],[15,1…...
猫咪如厕检测与分类识别系统系列【一】 功能需求分析及猫咪分类特征提取
开发背景 家里养了三只猫咪,其中一只布偶猫经常出入厕所。但因为平时忙于学业,没法时刻关注牠的行为。我知道猫咪的如厕频率和时长与健康状况密切相关,频繁如厕可能是泌尿问题,停留过久也可能是便秘或不适。为了更科学地了解牠的…...
粘性定位(position:sticky)——微信小程序学习笔记
1. 简介 CSS 中的粘性定位(Sticky positioning)是一种特殊的定位方式,它可以使元素在滚动时保持在视窗的特定位置,类似于相对定位(relative),但当页面滚动到元素的位置时,它会表现得…...
最新版IDEA超详细图文安装教程(适用Mac系统)附安装包及补丁2025最新教程
目录 前言 一、IDEA最新版下载 二、IDEA安装 三、IDEA补丁 前言 IDEA(IntelliJ IDEA)是专为Java语言设计的集成开发环境(IDE),由JetBrains公司开发,被公认为业界最优秀的Java开发工具之一。DEA全称Int…...
JavaWeb-04-Web后端基础(SpringBootWeb、HTTP协议、分层解耦、IOC和DI)
目录 一、SpringBootWeb入门 1.1 概述 1.2 入门程序 1.2.1 需求 1.2.2 开发步骤 1.3 入门解析 二、HTTP协议 2.1 HTTP概述 2.1.1 介绍 2.1.2 特点 2.2 HTTP请求协议 2.2.1 介绍 2.2.2 获取请求数据 2.3 HTTP响应协议 2.3.1 格式介绍 2.3.2 响应状态码 2.3…...
20250414| AI:RAG多路召回和融合重排序技术
好的!以下是对RAG(检索增强生成)中多路召回和融合重排序技术的详细解释,结合解释学习的视角,帮助你更好地理解和学习。这些技术是RAG系统的核心组成部分,决定了检索阶段的效果和最终生成答案的质量。我会尽…...
SQLite + Redis = Redka
Redka 是一个基于 SQLite 实现的 Redis 替代产品,实现了 Redis 的核心功能,并且完全兼容 Redis API。它可以用于轻量级缓存、嵌入式系统、快速原型开发以及需要事务 ACID 特性的键值操作等场景。 功能特性 Redka 的主要特点包括: 使用 SQLi…...
wkhtmltopdf 实现批量对网页转为图片的好工具,快速实现大量卡片制作
欢迎来到涛涛聊AI 1、需求痛点 在学习当中经常遇到一些知识点,想和大家分享。但只有文本形式,很多人不愿意去阅读,也看不到重点。 如果自己去单独设计页面版式,又太浪费时间。那就想着有没有一种方法,可以把一个知识…...
L1-6 大勾股定理
题目 大勾股定理是勾股定理的推广:对任何正整数 n 存在 2n1 个连续正整数,满足前 n1 个数的平方和等于后 n 个数的平方和。例如对于 n1 有 3^2 4^2 5^2 ;n2 有 10^2 11^2 12^2 13^2 14^2 等。给定 n,本题就请你找出对应的解。 输…...
Linux下Docker安装超详细教程(以CentOS为例)
前言 Docker 已成为现代应用开发和部署的标配工具。本教程将手把手教你 在 CentOS 系统上安装 Docker,涵盖从环境准备到验证安装的全流程,并解决常见问题。无论你是运维工程师还是开发者,均可快速上手。 一、环境要求 操作系统 CentOS 7 或更…...
深入解析xDeepFM:结合压缩交互网络与深度神经网络的推荐系统新突破
今天是周日,我来解读一篇有趣的文章——xDeepFM。这篇文章由 Mao et al. 发表在SIGIR 2019会议。文章提出了一个新的网络模型——压缩交互网络(CIN),用于显式地学习高阶特征交互。通过结合 CIN 和传统的深度神经网络(D…...
CST1017.基于Spring Boot+Vue共享单车管理系统
计算机/JAVA毕业设计 【CST1017.基于Spring BootVue共享单车管理系统】 【项目介绍】 共享单车管理系统,基于 Spring Boot Vue 实现,功能丰富、界面精美 【业务模块】 系统共有四类用户,分别是:监管用户、运营用户、调度用户、普…...
