Java ReentrantLock

目录

1 互斥性

2 公平性

3 可重入性

4 获取和释放锁

5 尝试获取锁

6 可中断的锁定

7 条件变量

8 性能

9 使用场景


ReentrantLock 是 Java 提供的一种可重入的互斥锁,位于 java.util.concurrent.locks 包中,它实现了 Lock 接口。这个锁提供了与内置监视器锁(通过 synchronized 关键字实现)类似的互斥性和内存可见性,但具有更强大的功能和灵活性。以下是 ReentrantLock 的一些基本概念:

1 互斥性

ReentrantLock 可以确保同一时间只有一个线程可以执行由该锁保护的代码块,从而避免了竞态条件。

2 公平性

  • 非公平锁:默认情况下,ReentrantLock 是非公平的。这意味着线程获取锁的顺序并不是按照它们请求锁的顺序来确定的。非公平锁通常提供更高的吞吐量。
  • 公平锁:当创建 ReentrantLock 实例时,可以通过传入 true 参数来指定为公平锁。公平锁保证线程将按照它们请求锁的顺序获得锁。这可能会降低吞吐量,但能减少饥饿现象。

示例代码:默认是非公平锁,如果需要公平锁,则在构造函数中传入 true

import java.util.concurrent.locks.ReentrantLock;// 默认非公平锁
ReentrantLock lock = new ReentrantLock();// 公平锁
ReentrantLock fairLock = new ReentrantLock(true);

3 可重入性

可重入意味着如果一个线程已经持有某个锁,那么它可以再次获取这个锁而不会被阻塞。这是非常有用的特性,因为它允许方法在调用其他可能也需要相同锁的方法时不会导致死锁。

示例代码:

public void reentrantExample() {lock.lock();try {// 第一次获取锁System.out.println("First time locked, thread: " + Thread.currentThread().getName());// 在已经持有锁的情况下再次获取锁lock.lock();try {System.out.println("Second time locked, thread: " + Thread.currentThread().getName());} finally {lock.unlock();  // 释放第二次获取的锁}} finally {lock.unlock();  // 释放第一次获取的锁}
}

输出结果:

First time locked, thread: main
Second time locked, thread: main

使用可重入锁的具体例子:假设有一个类,其中包含一个递归方法,且这个方法需要是线程安全的。由于方法会多次调用自身,因此需要一个可重入锁来确保同一个线程可以重复获取锁。

import java.util.concurrent.locks.ReentrantLock;public class FactorialCalculator {private final ReentrantLock lock = new ReentrantLock();public int factorial(int number) {lock.lock(); // 获取锁try {if (number <= 1) {return 1;} else {return number * factorial(number - 1); // 递归调用}} finally {lock.unlock(); // 释放锁}}
}

4 获取和释放锁

获取锁通过调用 lock() 方法,释放锁则通过 unlock() 方法。通常建议在 finally 块中释放锁,以确保即使发生异常也能正确释放锁。

public void doSomething() {lock.lock();try {// 执行同步代码} finally {lock.unlock();}
}

5 尝试获取锁

tryLock() 方法尝试获取锁,如果无法立即获得锁,则返回 false,不会阻塞线程。

if (lock.tryLock()) {  // 尝试获取锁,如果无法获取,则立即返回falsetry {// 执行同步代码} finally {lock.unlock();}
} else {// 锁未获得时的操作
}

tryLock() 方法有两个版本:

  • 无参数的 tryLock():尝试获取锁,如果锁可用则立即获取并返回 true,否则立即返回 false
  • 带超时的 tryLock(long timeout, TimeUnit unit):尝试获取锁,如果在指定的时间内可以获取到锁,则返回 true;如果超过了指定时间仍无法获取到锁,则返回 false

具体使用示例:

无参数的 tryLock():

import java.util.concurrent.locks.ReentrantLock;public class TryLockExample {private final ReentrantLock lock = new ReentrantLock();public void doWork() {if (lock.tryLock()) {  // 尝试获取锁try {// 执行需要同步的操作System.out.println("Thread " + Thread.currentThread().getName() + " is doing work.");} finally {lock.unlock();  // 确保释放锁}} else {// 如果无法获取锁,执行其他操作System.out.println("Thread " + Thread.currentThread().getName() + " could not get the lock and will do something else.");}}public static void main(String[] args) {TryLockExample example = new TryLockExample();// 创建两个线程来调用 doWork 方法Thread t1 = new Thread(() -> example.doWork(), "Thread-1");Thread t2 = new Thread(() -> example.doWork(), "Thread-2");t1.start();t2.start();}
}

在这个例子中,如果 Thread-1 先获取到了锁,那么 Thread-2 将无法获取锁,并且会直接输出“无法获取锁”的信息。

带超时的 tryLock(long timeout, TimeUnit unit) :

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;public class TryLockWithTimeoutExample {private final ReentrantLock lock = new ReentrantLock();public void doWork() {boolean locked = false;try {// 尝试在5秒内获取锁locked = lock.tryLock(5, TimeUnit.SECONDS);if (locked) {// 执行需要同步的操作System.out.println("Thread " + Thread.currentThread().getName() + " is doing work.");} else {// 如果在5秒内无法获取锁,执行其他操作System.out.println("Thread " + Thread.currentThread().getName() + " could not get the lock within 5 seconds and will do something else.");}} catch (InterruptedException e) {// 处理中断异常Thread.currentThread().interrupt();System.out.println("Thread " + Thread.currentThread().getName() + " was interrupted while waiting for the lock.");} finally {if (locked) {lock.unlock();  // 确保释放锁}}}public static void main(String[] args) {TryLockWithTimeoutExample example = new TryLockWithTimeoutExample();// 创建两个线程来调用 doWork 方法Thread t1 = new Thread(() -> example.doWork(), "Thread-1");Thread t2 = new Thread(() -> example.doWork(), "Thread-2");t1.start();t2.start();}
}

在这个例子中,如果 Thread-1 持有锁超过5秒钟,那么 Thread-2 将会在等待5秒后放弃尝试获取锁,并执行相应的逻辑。

使用场景

  • 避免死锁:当多个线程试图以不同的顺序获取多个锁时,可能会导致死锁。使用 tryLock() 可以帮助检测这种情况,并采取适当的措施。
  • 提高响应性:在某些情况下,你可能不希望线程一直等待锁,而是希望线程能够快速响应其他任务。这时可以使用 tryLock() 来检查锁是否可用,如果不可用就立即执行其他工作。
  • 资源竞争控制:在资源有限的情况下,可以使用 tryLock() 来尝试获取资源,如果获取不到则可以选择放弃或重试。

6 可中断的锁定

lockInterruptibly() 方法允许在等待获取锁的过程中响应中断。

try {lock.lockInterruptibly();  // 尝试获取锁,可以响应中断try {// 执行同步代码} finally {lock.unlock();}
} catch (InterruptedException e) {// 处理中断情况
}

示例代码:

import java.util.concurrent.locks.ReentrantLock;public class CancellableTask {private final ReentrantLock lock = new ReentrantLock();private boolean isCancelled = false;public void runTask() {Thread thread = new Thread(() -> {try {// 尝试获取锁,同时可以响应中断lock.lockInterruptibly();try {// 模拟任务执行while (!isCancelled) {System.out.println("Task is running...");// 假设任务需要一段时间完成Thread.sleep(1000);}} finally {lock.unlock();}} catch (InterruptedException e) {System.out.println("Task was interrupted, stopping execution.");// 通常在这里你会清理资源并退出// 重新设置中断状态,以便调用者知道该线程已被中断Thread.currentThread().interrupt();}});thread.start();// 在某个时刻决定取消任务try {Thread.sleep(3000); // 等待几秒钟} catch (InterruptedException e) {e.printStackTrace();}isCancelled = true;thread.interrupt(); // 中断线程}public static void main(String[] args) {CancellableTask task = new CancellableTask();task.runTask();}
}

当主线程决定取消任务时,它会中断工作线程。工作线程会在 lockInterruptibly() 调用处立即响应中断,并抛出 InterruptedException,从而允许任务快速终止。 

输出结果:

Task is running...
Task is running...
Task is running...
Task was interrupted, stopping execution.

除了使用 lockInterruptibly() 来实现中断锁,还可以使用带超时的 tryLock(long timeout, TimeUnit unit)来达成这一目的。以下是例子:

import java.util.concurrent.locks.ReentrantLock;public class ResourceService {private final ReentrantLock resourceLock = new ReentrantLock();public void useResource() {Thread thread = new Thread(() -> {try {if (resourceLock.tryLock()) {try {// 使用资源System.out.println("Using resource...");Thread.sleep(10000); // 模拟长时间操作} finally {resourceLock.unlock();}} else {System.out.println("Could not acquire the resource, operation is cancelled.");}} catch (InterruptedException e) {System.out.println("Resource usage was interrupted, releasing the resource.");resourceLock.unlock(); // 如果已经获得锁,则释放Thread.currentThread().interrupt(); // 保持中断状态}});thread.start();// 在某个时刻决定取消使用资源的操作try {Thread.sleep(5000); // 等待几秒钟} catch (InterruptedException e) {e.printStackTrace();}thread.interrupt(); // 中断线程}public static void main(String[] args) {ResourceService service = new ResourceService();service.useResource();}
}

是的,tryLock(long time, TimeUnit unit) 方法也可以响应中断。当一个线程调用 tryLock(long time, TimeUnit unit) 时,它会尝试在指定的时间内获取锁。如果在这段时间内没有获取到锁,方法将返回 false。此外,如果在此期间线程被中断,该方法会立即抛出 InterruptedException 并且不会获取锁。 

注意:ReentrantLocklock() 方法确实不会响应中断。当一个线程调用 lock() 试图获取锁时,如果锁已经被其他线程持有,那么该线程将一直阻塞,直到它能够获取到锁为止。即使在此期间该线程被中断(例如通过调用 Thread.interrupt()),它也不会抛出 InterruptedException 或者以其他方式退出等待状态。只有在获取到锁之后,中断状态才会被检查。

7 条件变量

ReentrantLock 提供了条件变量 Condition,它可以替代传统的 Object.wait/notify 机制。这些对象类似于 Object 类中的 wait/notify 机制,但是更加灵活。每个 Condition 实例都可以独立地挂起和唤醒线程,这对于复杂的同步需求是非常有用的。

下面是一个具体的例子,展示了如何使用条件变量来实现生产者-消费者模式

在这个模式中,有一个共享缓冲区,生产者向缓冲区添加元素,消费者从缓冲区移除元素。当缓冲区满时,生产者必须等待;当缓冲区空时,消费者必须等待。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.LinkedList;public class ProducerConsumerExample {private final int BUFFER_SIZE = 5; // 缓冲区大小private final LinkedList<Integer> buffer = new LinkedList<>();private final ReentrantLock lock = new ReentrantLock();private final Condition notFull = lock.newCondition(); // 缓冲区不满的条件private final Condition notEmpty = lock.newCondition(); // 缓冲区不空的条件public void put(int value) throws InterruptedException {lock.lock();try {// 如果缓冲区已满,则等待while (buffer.size() == BUFFER_SIZE) {notFull.await();}// 向缓冲区添加元素buffer.add(value);System.out.println("Produced: " + value);// 唤醒可能正在等待的消费者notEmpty.signal();} finally {lock.unlock();}}public int get() throws InterruptedException {lock.lock();try {// 如果缓冲区为空,则等待while (buffer.isEmpty()) {notEmpty.await();}// 从缓冲区移除元素int value = buffer.removeFirst();System.out.println("Consumed: " + value);// 唤醒可能正在等待的生产者notFull.signal();return value;} finally {lock.unlock();}}public static void main(String[] args) {final ProducerConsumerExample example = new ProducerConsumerExample();Thread producerThread = new Thread(() -> {for (int i = 0; i < 20; i++) {try {example.put(i);Thread.sleep(100); // 模拟生产时间} catch (InterruptedException e) {Thread.currentThread().interrupt();break;}}});Thread consumerThread = new Thread(() -> {for (int i = 0; i < 20; i++) {try {example.get();Thread.sleep(200); // 模拟消费时间} catch (InterruptedException e) {Thread.currentThread().interrupt();break;}}});producerThread.start();consumerThread.start();try {producerThread.join();consumerThread.join();} catch (InterruptedException e) {e.printStackTrace();}}
}

代码解释: 

  • ReentrantLock 和 Condition:我们使用 ReentrantLock 来保护对共享资源(即缓冲区)的访问,并且创建了两个条件变量 notFull 和 notEmpty
  • put() 方法:生产者调用此方法向缓冲区添加数据。如果缓冲区已满,则生产者将调用 notFull.await() 等待直到有空间可用。一旦添加了数据,就通过 notEmpty.signal() 通知等待中的消费者。
  • get() 方法:消费者调用此方法从缓冲区获取数据。如果缓冲区为空,则消费者将调用 notEmpty.await() 等待直到有数据可用。一旦取出了数据,就通过 notFull.signal() 通知等待中的生产者。
  • 主线程:启动生产者和消费者线程,并等待它们完成。

8 性能

ReentrantLock 在某些情况下比 synchronized 更高效,尤其是在高度竞争的情况下。这是因为 ReentrantLock 可以使用自旋而不是完全阻塞来等待锁,这样可以减少上下文切换的成本。

9 使用场景

  • 当你需要比 synchronized 更细粒度的控制时,例如尝试获取锁、响应中断或使用多个条件变量。
  • 当你需要实现公平锁时。
  • 当你希望在性能上有所提升,并且能够处理高级并发模式时。

示例代码:

import java.util.concurrent.locks.ReentrantLock;public class ReentrantLockExample {private final ReentrantLock lock = new ReentrantLock();public void doSomething() {// 获取锁lock.lock();try {// 执行需要同步的操作System.out.println("Thread " + Thread.currentThread().getName() + " is doing something.");} finally {// 确保锁最终会被释放lock.unlock();}}public static void main(String[] args) {ReentrantLockExample example = new ReentrantLockExample();// 创建两个线程来调用 doSomething 方法Thread t1 = new Thread(() -> example.doSomething(), "Thread-1");Thread t2 = new Thread(() -> example.doSomething(), "Thread-2");t1.start();t2.start();}
}

在这个例子中,doSomething 方法使用 ReentrantLock 来确保每次只有一个线程可以执行其中的代码。注意在 finally 块中释放锁,以防止因异常导致锁未被释放的情ss

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

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

相关文章

宿州儿童自闭症寄宿制学校:培养孩子独立能力的专业机构

在探索自闭症儿童教育的广阔领域里&#xff0c;宿州儿童自闭症寄宿制学校以其专业的教育模式和显著的成效&#xff0c;为众多家庭带来了希望。然而&#xff0c;当我们把目光投向中国南方的繁华都市——广州&#xff0c;会发现另一所同样在自闭症儿童教育领域深耕多年、成果显著…

python如何判断图片路径是否存在

1、在向文件夹中保存数据前&#xff0c;先判断该文件夹(路径)是否存在。 save_path /root/.../image/result if not os.path.exists(save_path):os.makedirs(save_path) 本来路径里只有到image文件夹的&#xff0c;执行完后会自动在image下创建result文件夹。 2、在打开某些图…

一款好用的图像处理软件:Photoshop

Photoshop 常被简称为PS&#xff0c;是图像处理领域里最常用也是很重要的一个工具。在平面广告设计、印刷出版等各领域有有着重要的作用。利用Photoshop图像处理软件&#xff0c;可以设计制作报纸、杂志、书籍、招贴广告、海报、建筑效果图、网页等各种精美的作品&#xff0c;普…

Thinkphp/Laravel基于Vue的重庆旅游网站交互设计与实现

目录 系统介绍具体实现截图技术栈和环境说明开发技术简介解决的思路性能/安全/负载方面数据访问方式PHP核心代码部分展示代码目录结构解析系统测试详细视频演示获取源码方式 系统介绍 本系统的设计与实现共包含12个表:分别是关于我们信息表&#xff0c;配置文件信息表&#xf…

车辆重识别(改进的去噪扩散概率模型)论文阅读2024/9/29

所谓改进的去噪扩散概率模型主要改进在哪些方面&#xff1a; ①对数似然值的改进 通过对噪声的那个方差和T进行调参&#xff0c;来实现改进。 ②学习 这个参数也就是后验概率的方差。通过数据分析&#xff0c;发现在T非常大的情况下对样本质量几乎没有影响&#xff0c;也就是说…

智慧应急指挥平台1+6+N体系建设方案

1. 智慧应急指挥平台概述 智慧应急指挥平台是一个综合性的应急响应体系&#xff0c;旨在通过高效的信息整合和通信技术&#xff0c;提升应急管理的智能化水平。该平台采用“16N”的体系结构&#xff0c;集成了智慧城市、智慧园区、智慧矿山等多个智慧应用&#xff0c;并依托三…

ONVIF、GB28181技术特点和使用场景分析

技术背景 好多开发者希望搞明白ONVIF和GB28181的区别和各自适合的场景&#xff0c;为什么大牛直播SDK只做了GB28181接入端&#xff0c;没有做ONVIF&#xff1f;本文就二者差别&#xff0c;做个大概的介绍。 ONVIF ONVIF&#xff08;Open Network Video Interface Forum&…

16.安卓逆向-frida基础-HOOK类方法2

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a;图灵Python学院 本人写的内容纯属胡编乱造&#xff0c;全都是合成造假&#xff0c;仅仅只是为了娱乐&#xff0c;请不要盲目相信。 工…

揭秘网络钓鱼:如何识破并防范这场数字时代的诈骗游戏

网络钓鱼是一种网络攻击&#xff0c;它利用伪装的电子邮件欺骗收件人提供信息、下载恶意软件或采取其他期望的行动。 网络钓鱼是网络害虫&#xff0c;自20世纪90年代初从暗网出现以来&#xff0c;至今仍危害全球。根据SlashNext的报告&#xff0c;2023年平均每天有31,000次网络…

解决$‘r‘ command not found或者文件夹显示’tvsf 33‘$‘r‘

问题现象: 某客户反馈在执行脚本的时候文件夹显示存在问题,如下图: 但是脚本文件中的内容并没有\r字符,如下图: 也有客户反馈如下: 问题分析: $\r’是回车符的转义表示。在Unix和Linux系统中,回车符是一个不可见的控制字符,它通常用于文本文件中的行结尾。以上…

国内ChatGPT镜像网站整理汇总【OpenAI o1/GPT 4o】-2024/10月最新

一、中文镜像站 ①yixiaai.com 支持GPT4、4o以及o1&#xff0c;支持MJ绘画、文件上传 ②chat.lify.vip 支持通用全模型&#xff0c;支持文件读取、插件、绘画、AIPPT ③AI Chat 支持GPT3.5/4&#xff0c;4o以及MJ绘画 1. 什么是镜像站 镜像站&#xff08;mirrored site&am…

NAL 网络提取层(Network Abstraction Layer)

1.NAL全称Network Abstract Layer, 即网络抽象层。 在H.264/AVC视频编码标准中&#xff0c;无论是存储还是网络传输&#xff0c;H264 原始码流是由一个接一个 NALU&#xff08;NAL Unit&#xff09; 组成&#xff0c;整个系统框架被分为两个层面&#xff1a;视频编码层面&#…

敏感内容识别是如何实现的?5大妙招教你快速筛选敏感词!

敏感内容识别是现代信息安全领域的重要课题&#xff0c;其实现依赖于多种技术和策略的综合运用。 以下是五种快速筛选敏感词的妙招&#xff1a; 一、关键词匹配 方法&#xff1a;通过使用安企神这类敏感词识别软件预设敏感词库&#xff0c;将待检测内容与敏感词库进行比对&am…

安达发|纺织行业APS系统中的物料替代解决方案

在纺织行业中&#xff0c;物料替代是应对原材料短缺、成本波动和供应链不确定性的一种重要策略。高级计划与排程系统&#xff08;APS&#xff09;通过集成物料替代功能&#xff0c;可以帮助企业在保持生产效率的同时&#xff0c;灵活应对市场变化。本文将探讨纺织行业在APS系统…

OpenFeign-快速使用-连接池-使用的最佳方案-日志配置

OpenFeign 我们利用Nacos实现了服务的治理,利用RestTemplate实现了服务的远程调用。但是远程调用的代码太复杂了: 其实远程调用的关键点就在于四个: 请求方式 请求路径 请求参数 返回值类型 所以,OpenFeign就利用SpringMVC的相关注解来声明上述4个参数,然后基于动态代理…

PHP人才机遇桥梁招聘求职全能系统小程序源码

人才机遇桥梁 —— 招聘求职全能系统全解析 &#x1f4bc;&#x1f680; &#x1f309; 搭建人才与机遇的桥梁 在这个竞争激烈的职场环境中&#xff0c;找到一份心仪的工作或招募到合适的人才&#xff0c;往往不是一件容易的事。但幸运的是&#xff0c;我们有了“人才机遇桥梁…

JDK1.8 新的特性

一 Lambda 使用 Lambda表达式应用场景&#xff1a;任何有函数式接口的地方&#xff0c;只有一个抽象方法(Object类中的方法除外)的接口是函数式接口。就像Runnable接口中&#xff0c;只有一个run方法。 1、Runnable //在JDK1.8之前的写法new Thread(new Runnable() {public v…

养老院管理系统(含源码+sql+视频导入教程+文档)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 养老院管理系统拥有两种角色&#xff1a;管理员和护工 管理员&#xff1a;用户管理、老人信息管理、事故记录管理、入住费用管理、护工薪资管理、护工请假管理、床位管理、请假管理等 护…

【机器学习基础】Transformer学习

Transformer学习 梯度消失FeedForward层激活函数的主要作用是在网络中加入非线性变换 梯度消失 梯度爆炸 FeedForward层 Transformer结构: Transformer结构主要分为两大部分: 一是Encoder层结构:Encoder 的输入由 Input Embedding 和 Positional Embedding 求和输入Multi…

大模型部署——NVIDIA NIM 和 LangChain 如何彻底改变 AI 集成和性能

DigiOps与人工智能 人工智能已经从一个未来主义的想法变成了改变全球行业的强大力量。人工智能驱动的解决方案正在改变医疗保健、金融、制造和零售等行业的企业运营方式。它们不仅提高了效率和准确性&#xff0c;还增强了决策能力。人工智能的价值不断增长&#xff0c;这从它处…