探索Java的ReentrantLock:实现并发锁的强大力量

news2024/11/25 6:40:18

前言
👏作者简介:我是笑霸final,一名热爱技术的在校学生。
📝个人主页:个人主页1 || 笑霸final的主页2
📕系列专栏:java系列
📧如果文章知识点有错误的地方,请指正!和大家一起学习,一起进步👀
🔥如果感觉博主的文章还不错的话,👍点赞👍 + 👀关注👀 + 🤏收藏🤏

目录

  • 一、ReentrantLock概述
  • 二、ReentrantLock的类结构图
  • 三、ReentrantLock(非公平锁)的实现过程解析
    • 3.1加锁lock.lock();
    • 3.2 加锁失败后acquire(1)尝试获取锁
      • 3.2.1 tryAcquire()再次尝试获取锁
      • 3.2.1 addWaiter() 添加到等待队列
      • 3.2.3 acquireQueued()阻塞方法
  • 四、思维导图

一、ReentrantLock概述

ReentrantLock是一种基于AQS框架的应用实现,是JDK中的一种线程并发访问的同步手段。它具有与synchronized类似的功能,但提供了比synchronized更强大、灵活的锁机制。
特点:

  • 可重入性:与synchronized一样,ReentrantLock也支持可重入锁。这意味着同一个线程可以多次获取同一个锁,只要在每次获取锁之前都释放了之前的锁。
  • 支持公平锁和非公平锁选择:ReentrantLock可以选择使用公平锁或非公平锁。公平锁按照线程请求锁的顺序进行分配,而非公平锁不保证按照顺序分配。
  • 支持可中断获取锁:使用ReentrantLock时,可以通过调用lockInterruptibly()方法来尝试获取锁,并在等待过程中能够被中断。
  • 支持设置超时时间:通过tryLock(long timeout, TimeUnit unit)方法,可以尝试在指定的超时时间内获取锁。
  • ReentrantLock内部实现了加锁的操作,并且支持重入锁。

AQS:

  • AQS使用一个FIFO(先入先出)的等待队列来实现线程之间的协作.
  • AQS提供了一些基本的同步控制方法,如acquire()、tryAcquire()、release()等
  • AQS内部维护了一个volatile的int型变量state,这个state代表着同步状态,通过CAS(CompareAndSwap)操作来实现同步状态的获取和释放。一般情况下state的值为0或1,分别表示锁未被持有或锁已被持有 (如ReentrantLock)。但在其他的同步器中,state的值可以代表不同的含义,比如Semaphore同步器中,state的值代表着可以获取的许可数量。

二、ReentrantLock的类结构图

在这里插入图片描述

  • RenentrantLock实现了Lock接口,Lock接口提供了锁的通用api,比如加锁lock,解锁unlock等操作。
  • RenentrantLock有一个内部类 Sync而FairSync和NonfairSync继承Sync来实现非公平锁和公平锁。
  • Node也是一个内部类表示等待队列中的节点,用于存储等待获取锁的线程。

三、ReentrantLock(非公平锁)的实现过程解析

3.1加锁lock.lock();

实际上底层调用的是sync.lock();方法这里我们讲非公平锁。

 final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

过程:
会先使用CAS改变State的值 从0到1,成功后 设置当前线程为独占线程,获取锁成功。
如果CAS失败 才会进入下面的流程 执行 acquire(1);尝试获取锁

3.2 加锁失败后acquire(1)尝试获取锁

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

可见会执行tryAcquire()addWaiter()acquireQueued()这三个方法我们来仔细看看。

3.2.1 tryAcquire()再次尝试获取锁

先看代码

protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }

进入nonfairTryAcquire

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

流程
先判断当前State的值是否为0

  • 进入if后 还是CAS获取锁
  • 获取锁成功 设置当前线程为独占线程 流程结束

获取锁失败
判断当前线程是否是 独占线程(可重入实现原理

  • 是当前线程 State+1 标记获取锁的次数 返回 true
  • 不是当前线程 就跳过当前if 直接返回 false

3.2.1 addWaiter() 添加到等待队列

注意
如果上一步返回为true 就不会执行此方法

 private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

可见 第一次 tail就是为null 不会进入if 而是先 enq(node);去创建队列
非第一次 就会CAS操作把当前节点设置为尾节点 成功入队 返回node 然后执行 acquireQueued()方法

看看enq(node)如何创建队列

private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

fo循环
先通过CAS创建队列 并设置头节点
然后第二次for循环 通过CAS入队 成功返回当前节点也 执行 acquireQueued()方法

3.2.3 acquireQueued()阻塞方法

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

也是for循环

  • final Node p = node.predecessor();获取当前队列头节点 如果当前是头节点又会执行tryAcquire(arg)获取锁
  • 如果获取锁失败 或者不是头节点 就会执行下面的if

shouldParkAfterFailedAcquire(p, node)

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
  • 先获取 当前 waitStatus 字段用于表示节点在等待队列中的状态。
    用来比对当前置节点的状态 结合外层for循环 进入 compareAndSetWaitStatusCAS操作前置节点的状态设置为 -1(waitStatus=-1
  • 最后调用 LockSupport.park(this);阻塞当前线程

四、思维导图

在这里插入图片描述

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

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

相关文章

单例模式:保证一个类仅有一个实例

单例模式&#xff1a;保证一个类仅有一个实例 欢迎来到设计模式系列的第二篇文章&#xff01;在上一篇中&#xff0c;我们已经对设计模式有了初步的了解&#xff0c;今天让我们深入研究第一个模式——单例模式。 什么是单例模式&#xff1f; 在软件开发中&#xff0c;我们经…

pyinstaller打包openvino 2021.4.2

打包准备 1. 测试环境准备 conda create -n opinstall python3.7 -y conda activate opinstall pip install openvino2021.4.2 pip install pyinstaller PyCharm新建openvino_install&#xff0c;选择虚拟环境opinstall&#xff0c;编写测试代码 app.py import numpy as n…

mysql基础下

文章目录 1.创建表和管理表1. 一条数据存储的过程2.MySQL中的数据类型3.创建表4.修改表5.重命名表6.删除表7.清空表 2.数据处理1. 插入数据2.更新数据3. 删除数据4. 将查询结果插入到表中 3. 约束1.约束概述2.非空约束3.唯一性约束4. 主键&#xff08;PRIMARY KEY&#xff09;约…

山西电力市场日前价格预测【2023-08-28】

日前价格预测 预测明日&#xff08;2023-08-28&#xff09;山西电力市场全天平均日前电价为319.70元/MWh。其中&#xff0c;最高日前电价为371.80元/MWh&#xff0c;预计出现在19: 15。最低日前电价为278.59元/MWh&#xff0c;预计出现在13: 00。 价差方向预测 1&#xff1a; …

秋招打卡016(0827)

文章目录 前言一、今天学习了什么&#xff1f;二、关于问题的答案1.牛客网面经2.美团后端一面3.动态规划 总结 前言 提示&#xff1a;这里为每天自己的学习内容心情总结&#xff1b; Learn By Doing&#xff0c;Now or Never&#xff0c;Writing is organized thinking. 先多…

基于海洋捕食者算法优化的BP神经网络(预测应用) - 附代码

基于海洋捕食者算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码 文章目录 基于海洋捕食者算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码1.数据介绍2.海洋捕食者优化BP神经网络2.1 BP神经网络参数设置2.2 海洋捕食者算法应用 4.测试结果&…

企业使用CRM如何统一销售流程管理?

销售流程我们可以理解为&#xff0c;销售人员从寻找潜在客户到最终达成交易的一系列步骤。很多企业通过CRM系统来进行销售流程管理&#xff0c;提高销售效率&#xff0c;实现销售目标。下面我们就来说说&#xff0c;CRM如何进行销售流程管理。 制定统一的销售流程&#xff1a;…

关于 Camera 预览和录像画质不一样的问题分析

1、问题背景 基于之前安卓平台的一个项目&#xff0c;客户有反馈过一个 Camera app 预览的效果&#xff0c;和录像效果不一致的问题。 这里的预览是指打开 Camera app 后直接出图的效果&#xff1b;录像的效果则是指打开 Camera app 开启录像功能&#xff0c;录制一段视频&…

【算法专题突破】双指针 - 快乐数(3)

目录 1. 题目解析 2. 算法原理 3. 代码编写 写在最后&#xff1a; 1. 题目解析 题目链接&#xff1a;202. 快乐数 - 力扣&#xff08;Leetcode&#xff09; 这道题的题目也很容易理解&#xff0c; 看一下题目给的示例就能很容易明白&#xff0c; 但是要注意一个点&#…

一篇带你了解npm的原理

npm 是 JavaScript世界的包管理工具,并且是 Node.js平台的默认包管理工具。通过 npm可以安装、共享、分发代码,管理项目依赖关系。 npm的原理 npm据称成为世界最大的包管理器?原因真的只是用户友好? 一、npm init 用来初始化一个简单的package.json文件。package.json文件…

9.3 功率放大电路的安全运行

在功率放大电路中&#xff0c;功放管既要流过大电流&#xff0c;又要承受高电压。例如&#xff0c;在 OCL 电路中&#xff0c;只有功放管满足式&#xff08;9.2.13&#xff09;所示极限值的要求&#xff0c;电路才能正常工作。因此&#xff0c;所谓功率放大电路的安全运行&…

微服务学习资料

文章目录 参考资料一. 微服务概述1. CAP理论2. BASE理论3. SpringBoot 与 SpringCloud对比 二. 服务注册&#xff1a;Zookeeper,Eureka,Nacos,Consul1. Nacos两种健康检查方式&#xff1f;2. nacos中负责负载均衡底层是如何实现的3. Nacos原理4. 临时实例和持久化(非临时)实例 …

【数据结构与算法篇】手撕八大排序算法之交换排序

​&#x1f47b;内容专栏&#xff1a; 《数据结构与算法篇》 &#x1f428;本文概括&#xff1a;常见交换排序包括冒泡排序与快速排序&#xff0c;本篇讲述冒泡排序与快速排序的思想及实现、复杂度分析。 &#x1f43c;本文作者&#xff1a; 花 蝶 &#x1f438;发布时间&#…

表和Json的相互操作

目录 一、表转Json 1.使用 for json path 2.如何返回单个Json 3.如何给返回的Json增加一个根节点呢 4.如何给返回的Json增加上一个节点 二、对Json基本操作 1.判断给的字符串是否是Json格式 2.从 JSON 字符串中提取标量值 3. 从 JSON 字符串中提取对象或数组 4. 更…

计网-All

路由器的功能与路由表的查看_路由器路由表_傻傻小猪哈哈的博客-CSDN博客路由基础-直连路由、静态路由与动态路由的概念_MikeVane-bb的博客-CSDN博客路由器的功能与路由表的查看_路由器路由表_傻傻小猪哈哈的博客-CSDN博客 直连路由就是路由器直接连了一个网段&#xff0c;他就…

在线考试系统java线上答题问答jsp源代码MySQL

本项目为前几天收费帮学妹做的一个项目&#xff0c;Java EE JSP项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、项目描述 在线考试系统 系统有2权限&#xff1a;管理员、学生 …

ChatGPT 与前端技术实现制作大屏可视化

像这样的综合案例实分析,我们可以提供案例,维度与指标数据,让ChatGPT与AIGC 帮写出完整代码,并进行一个2行2列的布局设置。 数据与指令如下: 商品名称 销量 目标 完成率 可乐 479 600 79.83% 雪碧 324 600 54.00% 红茶 379 600 63.…

NoSQL数据库介绍+Redis部署

目录 一、NoSQL概述 1、数据的高并发读写 2、海量数据的高效率存储和访问 3、数据库的高扩展和高可用 二、NoSQL的类别 1、键值存储数据库 2、列存储数据库 3、文档型数据库 4、图形化数据库 三、分布式数据库中的CAP原理 1、传统的ACID 1&#xff09;、A--原子性 …

webassembly001 webassembly简述

WebAssembly 官方地址:https://webassembly.org/相关历史 https://en.wikipedia.org/wiki/WebAssembly https://brendaneich.com/2015/06/from-asm-js-to-webassembly/WebAssembly&#xff08;缩写为Wasm&#xff09;是一种基于堆栈的虚拟机的二进制指令格式。Wasm 被设计为编…

【C语言练习】牛客题库第(一)期

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…