Java并发基石ReentrantLock:深入解读其原理与实现

在这里插入图片描述

码到三十五 : 个人主页

心中有诗画,指尖舞代码,目光览世界,步履越千山,人间尽值得 !


在Java的并发编程库中,ReentrantLock是一种非常重要的同步工具,它提供了一种比内置synchronized关键字更加灵活和可定制的锁定机制。在本文中,我们将详细讨论ReentrantLock的工作原理、特性以及如何使用它来解决多线程并发问题。

目录

    • 一、ReentrantLock概述
    • 二、ReentrantLock的核心特性
    • 三、ReentrantLock与synchronized
      • 3.1 相同点
      • 3.2 不同点
    • 四、ReentrantLock的使
    • 五、ReentrantLock的实现原理
      • 5.1 实现原理概述
      • 5.2 源码原理分析
    • 六、使用ReentrantLock的注意事项
    • 结语

一、ReentrantLock概述

ReentrantLock,也被称为“可重入锁”,是一个同步工具类,在java.util.concurrent.locks包下。这种锁的一个重要特点是,它允许一个线程多次获取同一个锁而不会产生死锁。这与synchronized关键字提供的锁定机制非常相似,但ReentrantLock提供了更高的扩展性。

二、ReentrantLock的核心特性

  1. 可重入性ReentrantLock的一个主要特点是它的名字所表示的含义——“可重入”。简单来说,如果一个线程已经持有了某个锁,那么它可以再次调用lock()方法而不会被阻塞。这在某些需要递归锁定的场景中非常有用。锁的持有计数会在每次成功调用lock()方法时递增,并在每次unlock()方法被调用时递减。
  2. 公平性:与内置的synchronized关键字不同,ReentrantLock提供了一个公平锁的选项。公平锁会按照线程请求锁的顺序来分配锁,而不是像非公平锁那样允许线程抢占已经等待的线程的锁。公平锁可以减少“饥饿”的情况,但也可能降低一些性能。
  3. 可中断性ReentrantLock的获取锁操作(lockInterruptibly()方法)可以被中断。这提供了另一个相对于synchronized关键字的优势,因为synchronized不支持响应中断。
  4. 条件变量ReentrantLock类中还包含一个Condition接口的实现,该接口允许线程在某些条件下等待或唤醒。这提供了一种比使用wait()notify()更灵活和更安全的线程通信方式。

三、ReentrantLock与synchronized

ReentrantLock与synchronized都是Java中用于多线程同步的机制,但它们在使用方式、功能和灵活性上有一些不同。

3.1 相同点

  1. 互斥性:ReentrantLock和synchronized都保证了一个时间点只有一个线程可以执行某个代码块,即它们都是互斥锁。
  2. 可重入性:两者都支持可重入性,意味着同一个线程可以多次获取同一个锁,不会产生死锁。

3.2 不同点

  1. 来源:synchronized是Java语言内建的关键字,而ReentrantLock是Java并发库java.util.concurrent.locks包中的一个类。

  2. 等待可中断性:ReentrantLock提供了一种能够响应中断的获取锁的方式(lockInterruptibly()),而synchronized是不可中断的,一旦线程没有获取到锁,就会进入阻塞状态,直到获取锁。

  3. 锁释放:ReentrantLock必须由手动释放锁(unlock()),所以使用起来需要特别小心,避免忘记释放锁导致死锁;而synchronized则是由JVM自动释放锁,当线程执行完同步代码块或方法后,JVM会自动释放线程持有的锁。

  4. 锁的申请:ReentrantLock提供了tryLock()方法,可以尝试获取锁,如果获取不到就返回false,不会一直等待;而synchronized没有这种机制,一旦获取不到锁,就会一直等待。

  5. 公平锁与非公平锁:ReentrantLock可以在构造函数中指定是公平锁还是非公平锁,而synchronized是非公平的,不保证等待时间最长的线程先获得锁。

  6. 绑定条件Condition:ReentrantLock可以与多个Condition对象绑定,以实现更细粒度的锁控制和线程间的协作;而synchronized没有这个功能,只能与整个对象绑定。

在这里插入图片描述

总的来说,ReentrantLock提供了比synchronized更灵活、更强大的锁机制,但使用起来也更复杂,需要更谨慎地处理锁的获取和释放。synchronized虽然功能相对简单,但在很多情况下已经足够使用,并且由于是内建关键字,使用起来也更方便。

四、ReentrantLock的使


下面代码模拟了一个账户转账的场景,展示了ReentrantLock如何保证多线程下的数据安全性。

import java.util.concurrent.locks.ReentrantLock;public class Account {// 账户余额private int balance;// 锁对象private final ReentrantLock lock = new ReentrantLock();public Account(int balance) {this.balance = balance;}// 存钱public void deposit(int amount) {lock.lock();  // 获取锁try {balance += amount;System.out.println("存入金额: " + amount + ",当前余额: " + balance);} finally {lock.unlock();  // 释放锁}}// 取钱public void withdraw(int amount) {lock.lock();  // 获取锁try {if (balance >= amount) {balance -= amount;System.out.println("取出金额: " + amount + ",当前余额: " + balance);} else {System.out.println("余额不足,取款失败!");}} finally {lock.unlock();  // 释放锁}}// 获取账户余额public int getBalance() {return balance;}// 主函数,模拟多线程下的转账操作public static void main(String[] args) {final Account account = new Account(1000);// 启动一个线程进行存钱操作new Thread(() -> {for (int i = 0; i < 5; i++) {account.deposit(100);try {Thread.sleep(100);  // 模拟延时} catch (InterruptedException e) {e.printStackTrace();}}}).start();// 启动一个线程进行取钱操作new Thread(() -> {for (int i = 0; i < 5; i++) {account.withdraw(50);try {Thread.sleep(100);  // 模拟延时} catch (InterruptedException e) {e.printStackTrace();}}}).start();}
}

Account类表示一个账户,包含了存钱(deposit)和取钱(withdraw)两个操作。为了保证账户余额在多线程环境下的数据安全性,我们在这两个方法中使用了ReentrantLock来确保同时只有一个线程能够修改账户余额。

deposit方法中,先获取锁,然后进行余额的增加操作,最后释放锁。同样地,在withdraw方法中也是先获取锁,然后判断余额是否足够,足够则进行扣款操作,否则输出提示信息,最后释放锁。

main方法中,创建了一个账户对象,并启动了两个线程,一个进行多次存钱操作,另一个进行多次取钱操作。由于我们使用了ReentrantLock来保证同步,因此即使在多线程环境下,账户的余额也不会出现不一致的情况。

五、ReentrantLock的实现原理

5.1 实现原理概述

ReentrantLock的实现依赖于内部的Sync类,这个类是AbstractQueuedSynchronizer(AQS)的一个实现。AQS是Java并发库中许多同步工具(包括SemaphoreCountDownLatchCyclicBarrier等)的核心。

AQS使用一个int类型的变量来表示同步状态,ReentrantLock用它来表示锁的持有计数和持有线程的信息。当计数为0时,表示锁未被任何线程持有。当一个线程首次成功获取锁时,JVM会记录这个锁的持有线程,并将计数器设置为1。如果同一个线程再次请求这个锁,它将能够再次获得这个锁,并且计数器会递增。当线程释放锁时(通过调用unlock()方法),计数器会递减。如果计数器递减为0,则表示锁已经完全释放,其他等待的线程有机会获取它。

在这里插入图片描述

此外,AQS还维护了一个队列,用于管理那些等待锁的线程。这个队列遵循FIFO原则,但也可以通过设置为公平锁来严格按照线程请求锁的顺序来排队。

5.2 源码原理分析

首先,ReentrantLock的核心实现是基于AbstractQueuedSynchronizer(AQS),它是一个用于构建锁和同步器的框架。ReentrantLock内部有一个静态内部类Sync,它继承了AQS并实现了所需的同步状态管理。

public class ReentrantLock implements Lock, java.io.Serializable {// 默认使用非公平锁private final Sync nonfairSync;// 公平锁private final Sync fairSync;// 抽象队列同步器,实际是nonfairSync或fairSyncprivate final Sync sync;// 构造函数,默认非公平锁public ReentrantLock() {sync = nonfairSync = new NonfairSync();}// 构造函数,可指定公平性public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}// 实现Lock接口的lock方法public void lock() {sync.lock();}// ... 其他方法,如tryLock, unlock等// 抽象队列同步器的实现abstract static class Sync extends AbstractQueuedSynchronizer {// ...// 是否处于占用状态final boolean isHeldExclusively() {return getState() == 1;}// 尝试获取锁final boolean tryAcquire(int acquires) {// ... 省略具体实现}// 释放锁protected final boolean tryRelease(int releases) {// ... 省略具体实现}// ... 其他方法}// 非公平锁实现static final class NonfairSync extends Sync {// ...// 锁获取final void lock() {// ... 省略具体实现}// ... 其他方法}// 公平锁实现static final class FairSync extends Sync {// ...// 锁获取,考虑公平性final void lock() {// ... 省略具体实现}// ... 其他方法}
}

上面的代码只是ReentrantLock的一个简版框架,通过这个框架,我们可以理解ReentrantLock的基本结构和关键组成部分:

  1. 同步状态管理ReentrantLock使用AQS的同步状态来管理锁的持有情况。当状态为0时,表示锁未被任何线程持有;当状态为1时,表示锁被某个线程持有。对于可重入锁,每次重入都会增加状态值,每次释放都会减少状态值。但这里简化的表示只用状态值1来表示锁被持有,实际实现中会有更复杂的状态管理。

  2. 锁的获取:在NonfairSyncFairSync中,lock()方法实现了锁的获取逻辑。非公平锁在尝试获取锁时不会考虑队列中的等待线程,而公平锁则会严格按照FIFO原则来处理等待线程。这些方法最终会调用AQS的acquire()方法,该方法会处理同步状态的变更、线程的阻塞和唤醒等。

  3. 锁的释放:锁的释放通过unlock()方法实现,最终会调用AQS的release()方法。这个方法会负责减少同步状态、唤醒等待线程等。在释放锁之前,必须确保当前线程是锁的持有者。

  4. 条件变量ReentrantLock还提供了newCondition()方法,用于创建条件变量。这些条件变量可以用于实现更复杂的线程同步和通信逻辑。条件变量的实现也是基于AQS的。

ReentrantLock的实现主要依赖于AQS框架,通过扩展AQS并实现特定的同步状态管理逻辑来实现可重入锁的功能。它提供了比synchronized关键字更灵活和可定制的同步机制,包括公平性选择、可中断的锁获取操作以及条件变量等。在使用ReentrantLock时,需要注意正确管理锁的获取和释放,以避免死锁和性能问题。

六、使用ReentrantLock的注意事项

  1. 始终在finally块中释放锁:为了确保锁能够在所有情况下都被正确释放(包括在可能抛出异常的代码中),你应该总是在finally块中调用unlock()方法。

  2. 避免锁泄露:锁泄露是指由于某些原因(如忘记释放锁或持有锁的线程意外死亡),导致锁无法被其他线程获取。这可能导致应用程序挂起或无法正常工作。使用try-finally语句可以帮助避免这种情况。

  3. 小心使用条件变量:虽然Condition接口提供了一种灵活的线程通信方式,但如果不当使用,也可能导致死锁或活锁等问题。你应该确保在使用条件变量时始终遵循正确的模式(如在调用await()方法之前检查条件,并在修改条件之后调用signal()signalAll()方法)。

  4. 公平性考虑:根据你的应用场景选择合适的锁公平性策略。虽然公平锁可以减少“饥饿”现象并提高可预测性,但它们也可能降低性能。另一方面,非公平锁可能会提供更好的性能,但在高竞争场景下可能导致线程“饥饿”。

  5. 性能考虑:与synchronized关键字相比,ReentrantLock在某些情况下可能提供更好的性能。但是,这也意味着你需要更小心地管理锁的获取和释放,以及处理可能出现的竞争和死锁问题。此外,过度使用锁(无论是synchronized还是ReentrantLock)都可能导致性能下降和可伸缩性问题。因此,在设计并发程序时,应该尽量使用无锁或低锁竞争的数据结构和算法。

结语

ReentrantLock 是 Java 提供的一种可重入的互斥锁,它具有与 synchronized 关键字类似的同步和锁定能力,但比 synchronized 更灵活。ReentrantLock支持中断获取锁、尝试获取锁(限时/非限时)和可轮询的获取锁等特性,适用于需要更高级锁定控制的场景。

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

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

相关文章

科研学习|研究方法——实验法

1.实验方法的渊源 今天我们说物理学、生物学是实验的科学&#xff0c;应该不会有人再持异议了&#xff0c;然而连物理学这样的学科在历史上也并非一开始就是实验科学。在2000多年以前的亚里士多德时代&#xff0c;众人都认为物理学是非实验性质的&#xff0c;物理学成为实验科学…

netty基础_12.用 Netty 自己实现简单的RPC

用 Netty 自己实现简单的RPC RPC 基本介绍我们的RPC 调用流程图己实现 Dubbo RPC&#xff08;基于 Netty&#xff09;需求说明设计说明代码封装的RPCNettyServerNettyServerHandlerNettyClientHandlerNettyClient 接口服务端(provider)HelloServiceImplServerBootstrap 客户端(…

第四百一十四回

文章目录 1. 概念介绍2. 思路与方法2.1 实现思路2.2 实现方法 3. 示例代码4. 内容总结 我们在上一章回中介绍了"自定义标题栏"相关的内容&#xff0c;本章回中将介绍自定义Action菜单.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在这里提到的…

【呼市经开区建设服务项目水、电能耗监测 数采案例】

实施方案 针对能耗采集中的水、电能源数据采集&#xff0c;因客观因素条件&#xff0c;数据采集方面存在较大难度。大多数国网电表485接口由于封签限制&#xff0c;不能实施采集&#xff0c;不让拆机接线&#xff0c;采集实施存在困难。水量能耗采集&#xff0c;存在类似问题&a…

腾讯云GPU服务器深度计算怎么收费?1小时、一个月和一年报价

腾讯云GPU服务器怎么收费&#xff1f;GPU服务器1小时多少钱&#xff1f;一个月收费价格表和一年费用标准&#xff0c;腾讯云百科txybk.com分享腾讯云GPU服务器GPU计算型GN10Xp、GPU服务器GN7、GPU渲染型 GN7vw等GPU实例费用价格&#xff0c;以及NVIDIA Tesla T4 GPU卡和V100详细…

Jmeter Ultimate Thread Group 和 Stepping Thread Group

线程组&#xff1a;使用复杂场景的性能测试 有时候我们做性能测试时&#xff0c;只依靠自带的线程组&#xff0c;显示满足不了性能测试中比较复杂的场景&#xff0c;下面这两种线程组可以帮助你很好的完成复杂的场景 第一种&#xff1a;Stepping Thread Group 在取样器错误后…

Socket类

2.2 Socket类 Socket 类&#xff1a;该类实现客户端套接字&#xff0c;套接字指的是两台设备之间通讯的端点。 构造方法 public Socket(String host, int port) :创建套接字对象并将其连接到指定主机上的指定端口号。如果指定的host是null &#xff0c;则相当于指定地址为回送…

Appium —— 移动应用自动化测试开源工具!

Appium介绍 Appium是一个用于自动化移动应用程序的开源工具&#xff0c;它支持iOS和Android平台。通过Appium&#xff0c;开发人员可以使用各种编程语言&#xff08;如Java、Python、Ruby等&#xff09;编写测试脚本&#xff0c;以自动化测试移动应用程序的功能和用户界面。Ap…

基于springboot+vue的小区团购管理

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

如何在Windows系统使用VS Code制作游戏网页并实现无公网IP远程访问

文章目录 前言1. 编写MENJA小游戏2. 安装cpolar内网穿透3. 配置MENJA小游戏公网访问地址4. 实现公网访问MENJA小游戏5. 固定MENJA小游戏公网地址 前言 本篇教程&#xff0c;我们将通过VS Code实现远程开发MENJA小游戏&#xff0c;并通过cpolar内网穿透发布到公网&#xff0c;分…

《操作系统实践-基于Linux应用与内核编程》第10章--实验 Qt聊天程序

前言: 内容参考《操作系统实践-基于Linux应用与内核编程》一书的示例代码和教材内容&#xff0c;所做的读书笔记。本文记录再这里按照书中示例做一遍代码编程实践加深对操作系统的理解。 引用: 《操作系统实践-基于Linux应用与内核编程》 作者&#xff1a;房胜、李旭健、黄…

微信小程序调试、断点调试

1、wxml 查看对应的页面组件 2、console面板可以用来打印信息 3、sources 用来断点调试 4、network面板用来调试接口 5、storage面板 可以查看每个key对应的value内容&#xff0c;这些数据在用户使用小程序时被持久化保存在本地。

【mac M3】idea删除不用或者失效的jdk

【mac M3】idea删除不用或者失效的jdk 不用&#xff08;重复&#xff09;或者失效的jdk如下&#xff1a; 重复或者已失效的JDK版本出现在下拉列表中不仅影响美观&#xff0c;也影响效率&#xff0c;删除jdk的步骤如下&#xff1a; 步骤1.点击File 步骤2.选择Project Structure…

【C语言】文件操作揭秘:C语言中文件的顺序读写、随机读写、判断文件结束和文件缓冲区详细解析【图文详解】

欢迎来CILMY23的博客喔&#xff0c;本篇为【C语言】文件操作揭秘&#xff1a;C语言中文件的顺序读写、随机读写、判断文件结束和文件缓冲区详细解析【图文详解】&#xff0c;感谢观看&#xff0c;支持的可以给个一键三连&#xff0c;点赞关注收藏。 前言 欢迎来到本篇博客&…

Java AOP 简单实例演示

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

WT32-ETH02 plus 串口转以太网开发,WT32-ETH01网关开发板升级款!

广受欢迎的WT32-ETH01网关开发板迎来了升级。 就是这款启明云端新推出的嵌入式串口转以太网开发板——WT32-ETH02 plus。应广大客户的需求&#xff0c;在WT32-ETH01的基础上增加了POE供电&#xff0c;可广泛应用于智能家居和网关等应用。开发板搭载2.4GHz Wi-Fi和蓝牙双模的SO…

一键部署灵境矩阵,属于自己的ai智能平台。

灵境矩阵 | 想象即现实 “灵境杯”智能体创意大赛&#xff0c;瓜分百万超级奖励 打造专属AI智能平台&#xff1a;一键部署灵境矩阵的无限可能 在数字化浪潮席卷全球的今天&#xff0c;人工智能技术已逐渐成为推动社会进步的关键力量。面对这一趋势&#xff0c;许多企业和个人…

永续合约多空双开“戴套”策略的逻辑是什么,胜率惊人的96%是怎么做到的,其实并没有想的那么复杂,会代码的都可以写出来

为什么叫多空双开“戴套”量化策略呢&#xff0c;因为这个策略的特点是永远有一个仓位是被套的&#xff0c;但是这个不影响我们盈利&#xff0c;具体怎么实现大家看下面这个图就明白是怎么回事了。 这个策略的逻辑很简单也容易理解&#xff0c;就是多空双开&#xff0c;盈利平仓…

FREERTOS空闲任务和低功耗

空闲任务 空闲任务是 FreeRTOS 必不可少的一个任务&#xff0c;其他 RTOS 类系统也有空闲任务&#xff0c;比如uC/OS。看名字就知道&#xff0c;空闲任务是处理器空闲的时候去运行的一个任务&#xff0c;当系统中没有其他就绪任务的时候空闲任务就会开始运行&#xff0c;空闲任…

slab分配器

什么是slab分配器&#xff1f; 用户态程序可以使用malloc及其在C标准库中的相关函数申请内存&#xff1b;内核也需要经常分配内存&#xff0c;但无法使用标准库函数&#xff1b;linux内核中&#xff0c;伙伴分配器是一种页分配器&#xff0c;是以页为单位的&#xff0c;但这个…