并发编程——ReentrantLock
如果有兴趣了解更多相关内容,欢迎来我的个人网站看看:耶瞳空间
一:基本介绍
从Java 5开始,引入了一个高级的处理并发的java.util.concurrent包,它提供了大量更高级的并发功能,能大大简化多线程程序的编写。我们知道Java语言直接提供了synchronized关键字用于加锁,但这种锁一是很重,二是获取时必须一直等待,没有额外的尝试机制。而java.util.concurrent.locks包提供的ReentrantLock用于替代synchronized加锁。
Lock是Java并发编程中很重要的一个接口,它要比synchronized关键字更能直译"锁"的概念,Lock需要手动加锁和手动解锁,一般通过lock.lock()方法来进行加锁,通过lock.unlock()方法进行解锁。一般会在finally块中写unlock( )以防死锁。而ReentrantLock实现了Lock接口。
ReentrantLock是一个可重入的互斥锁,又被称为“独占锁”。可重入表示ReentrantLock锁可以被同一个线程多次获取而不会出现死锁。ReentraantLock是通过一个FIFO的等待队列来管理获取该锁所有线程的。在“公平锁”的机制下,线程依次排队获取锁;而“非公平锁”在锁是可获取状态时,不管自己是不是在队列的开头都会获取锁。
ReentrantLock和synchronized比较
- synchronized是Java语言层面提供的语法,而ReentrantLock是Java代码实现的锁。
- synchronized是独占锁,加锁和解锁的过程自动进行,易于操作,但不够灵活。ReentrantLock也是独占锁,加锁和解锁的过程需要手动进行,不易操作,但非常灵活。
- synchronized不可响应中断,一个线程获取不到锁就一直等着;ReentrantLock可以响应中断。
基本使用示例:
public class Counter {private final Lock lock = new ReentrantLock();private int count;public void add(int n) {lock.lock();try {count += n;} finally {lock.unlock();}}
}
二:可重入
可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁。如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住
示例代码如下:
public class Demo {static Lock lock = new ReentrantLock();public static void main(String[] args) {method1();}public static void method1() {lock.lock();try {System.out.println("execute method1");method2();} finally {lock.unlock();}}public static void method2() {lock.lock();try {System.out.println("execute method2");} finally {lock.unlock();}}
}

三:可打断
public class Demo {static Lock lock = new ReentrantLock();public static void main(String[] args) {Thread t1 = new Thread(() -> {try {// 如果没有竞争,那么此方法就会获取lock对象锁// 如果有竞争,就会进入阻塞队列,可以被其他线程用interrupt方法打断System.out.println("尝试获得锁");lock.lockInterruptibly();} catch (InterruptedException e) {e.printStackTrace();// 没有获得锁,在等待时被打断了return;}try {System.out.println("获取到锁了");} finally {lock.unlock();}}, "t1");lock.lock();t1.start();try {Thread.sleep(1);System.out.println("打断t1");t1.interrupt();} catch (InterruptedException e) {e.printStackTrace();}}
}

四:锁超时
当tryLock方法里没有传入参数时,默认立刻尝试获得锁:
public class Demo {static Lock lock = new ReentrantLock();public static void main(String[] args) {Thread t1 = new Thread(() -> {System.out.println("t1线程启动。。。。。。");if (!lock.tryLock()) {System.out.println("t1线程获取锁失败,返回");return;}try {System.out.println("t1线程获得了锁");} finally {lock.unlock();}}, "t1");lock.lock();System.out.println("主线程获得了锁");t1.start();try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();} finally {System.out.println("主线程释放锁");lock.unlock();}}
}

但tryLock还有重载方法tryLock(long time, TimeUnit unit),该方法会等待时间内一直尝试获得锁:
public class Demo {static Lock lock = new ReentrantLock();public static void main(String[] args) {Thread t1 = new Thread(() -> {System.out.println("t1线程启动。。。。。。");try {if (!lock.tryLock(2, TimeUnit.SECONDS)) {System.out.println("t1线程获取锁失败,返回");return;}} catch (InterruptedException e) {System.out.println("等待被打断");e.printStackTrace();return;}try {System.out.println("t1线程获得了锁");} finally {lock.unlock();}}, "t1");lock.lock();System.out.println("主线程获得了锁");t1.start();try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();} finally {System.out.println("主线程释放锁");lock.unlock();}}
}

五:公平锁
公平锁与非公平锁:
- 公平锁的实现就是谁等待时间最长,谁就先获取锁
- 非公平锁就是随机获取的过程,谁运气好,cpu时间片轮询到哪个线程,哪个线程就能获取锁
ReentrantLock默认是不公平,但可以根据需要自行设置是否公平。ReentrantLock构造方法源码如下:
/*** 创建一个ReentrantLock实例* 该方法等同于调用ReentrantLock(false)*/public ReentrantLock() {sync = new NonfairSync();}/*** 根据传入的公平策略创建ReentrantLock实例* @param fair true为公平策略,false为非公平策略*/public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}
ReentrantLock公平锁相对于非公平锁来说,多线程并发情况下的系统吞吐量偏低,因为需要排队等待。所以ReentrantLock公平锁适应于多线程并发不是很高、倾向于先来先到的应用场景。
六:条件变量
ReentrantLock中的条件变量功能,类似于普通synchronized的wait、notify,我们可以使用ReentranLlock锁,配合Condition对象上的await()和signal()或signalAll()方法,来实现线程间协作。与synchronized的wait和notify不同之处在于,ReentrantLock中的条件变量可以有多个,可以实现更精细的控制线程。
在介绍方法的使用之前,先来了解一下Condition是什么。可以把Condition看作是Object监视器的替代品。众所周知,Object有wait()和notify()方法,用于线程间的通信。并且这两个方法只能在synchronized同步块内才可以调用,所有线程的等待和唤醒都需要关联到监视器对象的WaitSet集合。Condition同样可以实现上面的线程通信。不同点在于,synchronized锁对象关联的监视器对象仅有一个,所以等待队列也只有一个。而一个ReentrantLock可以有多个Condition,这样可以根据不同的业务需求,在使用同一个lock锁对象的基础上使用多个等待队列,让不同性质的线程加入到不同的等待队列当中。
AQS当中Condition的实现类是ConditionObject,它是AQS的内部类,所以无法直接实例化。可以配合ReentrantLock来使用。ReentrantLock中有newCondition()的方法,来实例化一个ConditionObject对象,因此可以调用多次newCondition()方法来得到多个等待队列。
使用流程:
- await前需要获得锁
- await 执行后,会释放锁,进入conditionObject等待
- await的线程被唤醒(或打断、或超时)取重新竞争lock锁竞争lock锁成功后,从await后继续执行
示例代码如下:
@Slf4j
public class Demo {private static ReentrantLock lock = new ReentrantLock();// 等烟休息室static Condition cigaretteRoom = lock.newCondition();// 等外卖休息室static Condition eattingRoom = lock.newCondition();static boolean hasCigarette = false;static boolean hasTakeout = false;public static void main(String[] args) throws InterruptedException {// 小南new Thread(() -> {lock.lock();try {log.debug("[{}]", hasCigarette);while (!hasCigarette) {log.debug("没烟,先歇会!");try {cigaretteRoom.await();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("有烟没?[{}]", hasCigarette);if (hasCigarette) {log.debug("可以开始干活了");}} finally {lock.unlock();}}, "小南").start();// 小女等外卖new Thread(() -> {lock.lock();try {log.debug("外卖送到没?[{}]", hasTakeout);while (!hasTakeout) {log.debug("没外卖,先歇会!");try {eattingRoom.await();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("外卖送到没?[{}]", hasTakeout);if (hasTakeout) {log.debug("可以开始干活了");} else {log.debug("没干成活...");}} finally {lock.unlock();}}, "小女").start();// 送烟的来了Thread.sleep(1000);new Thread(() -> {lock.lock();try {hasCigarette = true;cigaretteRoom.signal();} finally {lock.unlock();}}, "送烟的").start();// 送外卖的来了Thread.sleep(1000);new Thread(() -> {lock.lock();try {hasTakeout = true;eattingRoom.signal();} finally {lock.unlock();}}, "送外卖的").start();}
}
相关文章:
并发编程——ReentrantLock
如果有兴趣了解更多相关内容,欢迎来我的个人网站看看:耶瞳空间 一:基本介绍 从Java 5开始,引入了一个高级的处理并发的java.util.concurrent包,它提供了大量更高级的并发功能,能大大简化多线程程序的编写…...
English Learning - L2 第 3 次小组纠音 [ʌ] [ɒ] [ʊ] [ɪ] [ə] [e] 2023.3.4 周六
English Learning - L2 第 3 次小组纠音 [ʌ] [ɒ] [ʊ] [ɪ] [ə] [e] 2023.3.4 周六共性问题小元音 [ʌ]小元音 [ɒ]小元音 [ʊ]小元音 [ɪ]小元音 [ə]小元音 [e]我的发音问题纠音过程共性问题 小元音 [ʌ] 口型容易偏大 解决办法:因为嘴角没有放松,…...
STM32之关门狗
看门狗介绍在由单片机构成的微型计算机系统中,由于单片机的工作常常会受到来自外界电磁场的干扰,造成程序的跑飞,而陷入死循环,程序的正常运行被打断,由单片机控制的系统无法继续工作,会造成整个系统的陷入…...
Apollo控制部分1-- ControlComponent组件介绍
Apollo控制部分1-- ControlComponent组件介绍摘要一、ControlComponent1、启动文件解析2、ControlComponent()组件函数解析1)ControlComponent::ControlComponent() 构造函数2)ControlComponent::Init() 初始化函数(执行一次)3&am…...
0626-0631韩顺平Java Buffered字节处理流 学习笔记
如何去构建字节流package com.hspedu.outputstream_;import java.io.*;/*** author abner* version 1.0*/ public class BufferedCopy02 {public static void main(String[] args) {String srcFilePath "D:\\Users\\Pictures\\Camera Roll\\Pierre-Auguste_Renoir,_Le_Mo…...
【网络】序列化和反序列化
🥁作者: 华丞臧. 📕专栏:【网络】 各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞收藏关注)。如果有错误的地方,欢迎在评论区指出。 推荐一款刷题网站 👉 LeetCode刷题网站 文章…...
【代码随想录训练营】【Day32】第八章|贪心算法|122.买卖股票的最佳时机II |55. 跳跃游戏|45.跳跃游戏II
买卖股票的最佳时机II 题目详细:LeetCode.122 买卖股票的最佳时机,怎么都能够想出来个思路,假如我们每天都能预知明天的股票是涨是降,那么贪心策略就是在涨之前买股票,在降的前一天卖掉,这就是买卖股票的…...
constexpr 和 常量表达式
👀👀常量表达式 常量表达式是指值不会改变并且在编译过程就能得到计算结果的表达式。 字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式。 那么是什么来就决定是不是常量表达式呢?一个对象是不是常量表达式主要…...
Vue响应式原理————Object.defineProperty()和proxy的用法分享
Vue框架一个比较核心的功能就是我们的数据是响应式的,这样我们在修改数据的时候,页面会自动帮我们更新,那么想要实现这个功能就要实现对一个数据的劫持,即在取值和设置值的同时我们能够检测到即数据劫持。vue2响应式的实现原理所依…...
CSDN 编程竞赛三十四期题解
竞赛总览 CSDN 编程竞赛三十四期:比赛详情 (csdn.net) 本期的题目和第三十一期竞赛的题目竟然高度重合,真不知道该写点什么了。 不过,上次那道测试数据有bug的题已经修复了,答题过程挺顺利的,没有遇到新的问题。 竞…...
C#教程06 运算符
文章目录 一、算术运算符加法运算符(+)减法运算符(-)乘法运算符(*)除法运算符(/)二、逻辑运算符与运算符(&&)或运算符(||)非运算符(!)三、比较运算符等于运算符(==)大于运算符(>)小于运算符(<)大于等于运算符(>=)小于等于运算符(<=…...
软测入门(六)pytest单元测试
pytest pytest是python的一种单元测试框架,同自带的unit test测试框架类似,但pytest更简洁高效。 单元测试: 测试 函数、类、方法能不能正常运行测试的结果是否符合我们的预期结果 安装 pip install -U pytest基本使用 通过pytest包使用…...
经典分类模型回顾5—DenseNet实现图像分类(matlab)
DenseNet,全称为Densely Connected Convolutional Networks,中文名为密集连接卷积网络,是由李沐等人在2017年提出的一种深度神经网络架构。 DenseNet旨在解决深度神经网络中的梯度消失问题和参数数量过多的问题,通过构建密集连接…...
基于flask+bootstrap+echarts+mysql的鱼村小馆订餐后台管理系统
📋 个人简介 💖 作者简介:大家好,我是阿牛,全栈领域优质创作者。😜📝 个人主页:馆主阿牛🔥🎉 支持我:点赞👍收藏⭐️留言Ὅ…...
Spark使用Log4j将日志发送到Kafka
文章目录自定义KafkaAppender修改log4j.properties配置启动命令配置添加参数启动之后可以在Kafka中查询发送数据时区问题-自定义实现JSONLayout解决自定义JSONLayout.java一键应用可能遇到的异常ClassNotFoundException: xxx.KafkaLog4jAppenderUnexpected problem occured dur…...
c++类与对象整理(上)
目录 1.类的引入 2.类的定义 3.类的访问限定符及封装 1)访问限定符 2)封装 4.类的作用域 5.类的实例化 6.类的对象大小的计算 1)类对象的存储方式 2)内存对齐和大小计算 编辑 7.类成员函数的this指针 1)…...
Docker学习(十九)什么是镜像的元数据?
在 Docker 中,镜像的元数据是指与镜像相关的所有信息,包括镜像的名称和标签、作者、描述、创建日期、环境变量、命令等。这些信息都是通过 Dockerfile 或命令行创建和指定的。 镜像的元数据被存储在 Docker Registry 中,并在使用 docker pull…...
Python如何获取弹幕?给你介绍两种方式
前言 弹幕可以给观众一种“实时互动”的错觉,虽然不同弹幕的发送时间有所区别,但是其只会在视频中特定的一个时间点出现,因此在相同时刻发送的弹幕基本上也具有相同的主题,在参与评论时就会有与其他观众同时评论的错觉。 在国内…...
JAVA- AOP 面向切面编程 Aspect切面工具类 记录特定方法执行时的入参、执行时间、返参等内容
背景:JAVA项目,使用AOP对指定函数进行切面。能够记录特定方法执行时的入参、执行时间、返参结果等内容。 文章目录1、自定义注解类1.1 Target1.2 Retention2、Aspect切面工具2.1 JointPoint2.2 Pointcut2.3 切面中的相关注解3、同一个类里调用AOP4、其他…...
「史上最全的 TCG 规范解读」TCG 规范架构概述(下)
可信计算组织(Ttrusted Computing Group,TCG)是一个非盈利的工业标准组织,它的宗旨是加强不同计算机平台上计算环境的安全性。TCG 于 2003 年春成立,并采纳了由可信计算平台联盟(the Trusted Computing Platform Allia…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI
前一阵子在百度 AI 开发者大会上,看到基于小智 AI DIY 玩具的演示,感觉有点意思,想着自己也来试试。 如果只是想烧录现成的固件,乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外,还提供了基于网页版的 ESP LA…...
2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http…...
【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
在Mathematica中实现Newton-Raphson迭代的收敛时间算法(一般三次多项式)
考察一般的三次多项式,以r为参数: p[z_, r_] : z^3 (r - 1) z - r; roots[r_] : z /. Solve[p[z, r] 0, z]; 此多项式的根为: 尽管看起来这个多项式是特殊的,其实一般的三次多项式都是可以通过线性变换化为这个形式…...
Linux系统部署KES
1、安装准备 1.版本说明V008R006C009B0014 V008:是version产品的大版本。 R006:是release产品特性版本。 C009:是通用版 B0014:是build开发过程中的构建版本2.硬件要求 #安全版和企业版 内存:1GB 以上 硬盘…...
tauri项目,如何在rust端读取电脑环境变量
如果想在前端通过调用来获取环境变量的值,可以通过标准的依赖: std::env::var(name).ok() 想在前端通过调用来获取,可以写一个command函数: #[tauri::command] pub fn get_env_var(name: String) -> Result<String, Stri…...
解析两阶段提交与三阶段提交的核心差异及MySQL实现方案
引言 在分布式系统的事务处理中,如何保障跨节点数据操作的一致性始终是核心挑战。经典的两阶段提交协议(2PC)通过准备阶段与提交阶段的协调机制,以同步决策模式确保事务原子性。其改进版本三阶段提交协议(3PC…...
js 设置3秒后执行
如何在JavaScript中延迟3秒执行操作 在JavaScript中,要设置一个操作在指定延迟后(例如3秒)执行,可以使用 setTimeout 函数。setTimeout 是JavaScript的核心计时器方法,它接受两个参数: 要执行的函数&…...
