ReentrantLock源码浅析

一、ReentrantLock概念

ReentrantLock是JAVA并发情况下提供的用来加锁的机制,位于JUC包下,提供了一系列的加锁释放锁的方法,使用起来非常简单,只需要在代码块之前调用lock()方法,在finally中调用unlock()方法即可解决并发的问题。

1.1、AQS

AQS实际上是AbstractQueuedSynchronizer的缩写,是JAVA提供的用来实现锁的一个抽象类,它有一个基类AbstractOwnableSynchronizer,可以理解为它为实现ReentrantLock等其他锁提供了一个基础的容器,这些容器在不同的锁的实现中都存放着实现这些锁所需要的基本组件。

  • 1、Node:AQS中的内部类,代表着一个节点,内部有一个Thread线程,每一个线程竞争锁的时候,都会被封装成一个Node放在队列中排队。
  • 2、exclusiveOwnerThread:这是AQS的基类中的变量,用来表示当前已经竞争到锁的线程,
  • 3、state:AQS中的变量,是一个volatile int类型的变量,线程能不能竞争到锁,就看线程能不能通过CAS的方式将该变量从0改成1。
  • 4、head:AQS中的变量,是一个volatile Node类型的变量,用来标识头结点。
  • 5、tail:AQS中的变量,是一个volatile Node类型的变量,用来标识尾节点。
1.2、CAS

CAS相关知识,参考本人博客:JAVA CAS问题原理

二、ReentrantLock源码流程

2.1、lock():

实际上会调用sysn的lock(),而sync在ReentrantLock中有两种实现,也就是FairSync(公平锁)和NonfairSync(非公平锁),默认为NonfairSync。

public void lock() {  
	// 默认调用NonfairSync中的lock()
    sync.lock();  
}

2.2、NonfairSync中的lock()
final void lock() { 
	// 因为是非公平锁,一上来就先竞争锁,
    if (compareAndSetState(0, 1))  
	    // 如果获取到锁,就将exclusiveOwnerThread变量设置为自己,表明当前是自己获取到锁了。
        setExclusiveOwnerThread(Thread.currentThread());  
    else  
	    // 如果没有获取到锁,那么就开始排队
        acquire(1);  
}
2.3、acquire()方法是AQS中提供的方法
public final void acquire(int arg) {  
	// 再次尝试获取锁,如果获取锁失败那么就将当前线程封装成Node节点开始排队
    if (!tryAcquire(arg) &&  
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 
        // 并且将当前线程设置为停止状态,也就是说,放弃竞争了。 
        selfInterrupt();  
}
2.4、addWaiter()方法是AQS中提供的方法
private Node addWaiter(Node mode) {  
	// 首先将当前线程封装成一个Node节点
    Node node = new Node(Thread.currentThread(), mode);  
    // 存储头结点
    Node pred = tail;  
    // 如果头结点不为null,
    if (pred != null) { 
	    // 当前节点的前一个节点指向tail
        node.prev = pred;  
        // 并且通过CAS的方式将当前节点和tail进行交换
        if (compareAndSetTail(pred, node)) {  
	        // 如果当前节点和tail进行交换成功,头结点的下一个节点指向当前节点,并返回当前节点
            pred.next = node;  
            return node;  
        }  
    } 
    // 如果头节点为空,说明当前节点是第一个节点,队列中没有其他节点 
    enq(node);  
    return node;  
}
2.5、enq()方法是AQS中提供的方法
private Node enq(final Node node) {  
    for (;;) {  
	    // 将头节点进行缓存
        Node t = tail;  
        // 如果头结点为null,说明此时队列中没有其他节点,只有当前节点一个
        if (t == null) {
	        // 创建一个新的节点,该节点没有任何意义,只是AQS实现上为了帮助后续唤醒线程而设计
            if (compareAndSetHead(new Node()))  
	            // 头尾指向同一个节点
                tail = head;  
        } else {  
	         // 如果头结点不为null,当前节点的前一个节点指向tail
            node.prev = t;  
            // 通过CAS方式设置当前节点为头节点
            if (compareAndSetTail(t, node)) {
	            // 头结点的下一个节点指向当前节点 
                t.next = node;  
                return t;  
            }  
        }  
    }  
}
2.6、acquireQueued()方法是AQS中提供的方法
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); 
                // 将当前节点的前一个节点(实际上就是头结点)设置为null,
                p.next = null; // help GC  
                failed = false; 
                // 只有在竞争不到锁的情况下,才是true,表明停止当前线程,放弃竞争锁
                return interrupted;  
            }  
            // 当线程竞争锁失败后,是不是需要暂停线程
            if (shouldParkAfterFailedAcquire(p, node) &&  
                parkAndCheckInterrupt())  
                interrupted = true;  
        }  
    } finally {  
        if (failed) 
            // 当线程竞争锁失败后,需要取消竞争 
            cancelAcquire(node);  
    }  
}

2.7、nonfairTryAcquire()是Sync中提供的竞争锁的方法。
final boolean nonfairTryAcquire(int acquires) { 
	// 获取当前线程
    final Thread current = Thread.currentThread();  
    // 获取state变量值
    int c = getState(); 
    // 如果state变量值为0,说明当前没有线程竞争锁或者锁已经被释放,那么就代表,当前线程可以竞争锁 
    if (c == 0) { 
		// 通过CAS操作将state将由0改成1,
        if (compareAndSetState(0, acquires)) {  
	        // 如果成功,就获取到锁,将当前线程设置为自己并返回true,表明加锁成功。
            setExclusiveOwnerThread(current);  
            return true;  
        }  
    }  // 如果state的值不为0,那么就是判断当前线程是不是自身线程,如果是,说明本身已经获取到锁了,就不在需要重复竞争锁了,实际上就是可重入锁的判断
    else if (current == getExclusiveOwnerThread()) {  
	    // 可重入次数的判断,实际上ReentrantLock中不可能出现这种情况,这里知识容错处理
        int nextc = c + acquires;  
        if (nextc < 0) // overflow  
            throw new Error("Maximum lock count exceeded");  
         // 将state的值设置为重入的次数,并返回true,表明加锁成功。
        setState(nextc);  
        return true;  
    }  
    // 否则其他情况都是加锁失败的情况
    return false;  
}

三、ReentrantLock总结

1、ReentrantLock可以实现公平锁和非公平锁以及可重入锁的机制,基于AQS和CAS进行加锁处理, 提供了lock和unlock两个api方法进行加锁和释放锁处理。
2、以非公平锁为例,lock方法一进去就竞争锁,通过CAS的方式将state变量从0设置为1,如果设置成功,表明竞争锁成功,就将线程变量设置为当前线程,如果失败,就开始排队。
3、再次尝试获取锁(tryAcquire),如果继续失败,那么就放弃竞争锁,将自己状态设置为interrupted的。并且开始排队。
4、排队的时候,将自身线程封装成一个Node节点,找到一个不失效的前继节点,当前节点的pred指向前继节点,前继节点的next指向当前节点。
4、可重入的判断,实际上,如果发现当前state不为0,那就说明有线程正在占用资源,那么只需要判断current == getExclusiveOwnerThread()是否成立即可,如果成立就是可重入的,获得锁,如果不成立,就说明,当前占用资源的线程不是自身,获取锁失败。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

迭代新品 | 第四代可燃气体监测仪,守护燃气管网安全快人一步

城市地下市政基础设施是城市有序运行的生命线&#xff0c;事关城市安全、健康运行和高质量发展。近年来&#xff0c;我国燃气事故多发、频发。2020、2021、2022 年分别发生燃气事故668、1140 起、802 起&#xff0c;造成92、106、66 人死亡&#xff0c;560、763、487 人受伤。尤…

「C++」map和set的使用介绍

&#x1f4bb;文章目录 &#x1f4c4;前言前置知识关联式容器键值对map和set的底层结构 setset的构造函数set 的修改操作set的使用 mapmap的函数map的使用 multiset 和 multimap&#x1f4d3;总结 &#x1f4c4;前言 stl容器分为两类&#xff0c;分别是序列容器和关联式容器&am…

Java 高等院校分析与推荐系统

1&#xff09;项目简介 随着我国高等教育的大众化&#xff0c;高校毕业生就业碰到了前所未有的压力&#xff0c;高校学生就业问题开始进入相关研究者们的视野。在高校学生供给忽然急剧增加的同时&#xff0c;我国高校大学生的就业机制也在发生着深刻的变化&#xff0c;作为就业…

操作系统:进程(一)

进程的基本概念 一般的解释是&#xff1a;进程是程序的一个执行实例&#xff0c;是正在执行的程序。我们写的程序编译后是一段二进制的文件。启动的时候加载到系统里面执行&#xff0c;就是以进程的形式执行。也就是说&#xff0c;我们编译后的可执行程序是一个静态的概念&…

C++ STL之string初始

我最近开了几个专栏&#xff0c;诚信互三&#xff01; > |||《算法专栏》&#xff1a;&#xff1a;刷题教程来自网站《代码随想录》。||| > |||《C专栏》&#xff1a;&#xff1a;记录我学习C的经历&#xff0c;看完你一定会有收获。||| > |||《Linux专栏》&#xff1…

JSP基本表单和Request对象使用例子

表单的jsp&#xff1b; <%page contentType"text/html;charsetgbk" pageEncoding"UTF-8"%> <!DOCTYPE html> <html><head><meta http-equiv"Content-Type" content"text/html; charsetUTF-8"><titl…

golang学习笔记——接口interfaces

文章目录 Go 语言接口例子空接口空接口的定义空接口的应用空接口作为函数的参数空接口作为map的值 类型断言接口值 类型断言例子001类型断言例子002 Go 语言接口 接口&#xff08;interface&#xff09;定义了一个对象的行为规范&#xff0c;只定义规范不实现&#xff0c;由具…

数据库大事记

数据库分类分类方法为&#xff1a;按数据模型分类、按业务类型分类、按部署方式分类、按存储介质分类。 按数据模型分类 按业务类型分类 按部署方式分类 按存储介质分类 喜欢点赞收藏&#xff0c;下期再见。

【Redux】Redux 基本使用

1. Redux 快速上手 Redux 是 React 最常用的集中状态管理工具&#xff0c;类似于Vue中的Pinia&#xff08;Vuex&#xff09;&#xff0c;可以独立于框架运行。 <button id"decrement">-</button> <span id"count">0</span> <…

多线程Thread(初阶一:认识线程)

目录 一、引用线程的原因 二、线程的概念 三、进程和线程的区别 四、多线程编程 一、引用线程的原因 多任务操作系统&#xff0c;希望系统能同时运行多个任务。所以会涉及到进程&#xff0c;需要对进程进行管理、调度等。 而单任务操作系统&#xff0c;就完全不涉及到进程…

YOLOv8-Seg改进策略:全新的聚焦式线性注意力模块Focused Linear Attention | ICCV2023

🚀🚀🚀本文改进:深入分析了现有线性注意力方法的缺陷,并提出了一个全新的聚焦的线性注意力模块(Focused Linear Attention),同时具有高效性和很强的模型表达能力。 🚀🚀🚀YOLOv8-seg创新专栏:http://t.csdnimg.cn/KLSdv 学姐带你学习YOLOv8,从入门到创新,…

深度学习系列53:mmdetection上手

1. 安装 使用openmim安装&#xff1a; pip install -U openmim mim install "mmengine>0.7.0" mim install "mmcv>2.0.0rc4"2. 测试案例 下载代码和模型&#xff1a; git clone https://github.com/open-mmlab/mmdetection.git mkdir ./checkpoi…

2023年【熔化焊接与热切割】考试试卷及熔化焊接与热切割试题及解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 熔化焊接与热切割考试试卷考前必练&#xff01;安全生产模拟考试一点通每个月更新熔化焊接与热切割试题及解析题目及答案&#xff01;多做几遍&#xff0c;其实通过熔化焊接与热切割模拟考试很简单。 1、【单选题】 对…

React+后端实现导出Excle表格的功能

最近在做一个基于Reactantd前端框架的Excel导出功能&#xff0c;我主要在后端做了处理&#xff0c;这个功能完成后&#xff0c;便总结成一篇技术分享文章&#xff0c;感兴趣的小伙伴可以参考该分享来做导出excle表格功能&#xff0c;以下步骤同样适用于vue框架&#xff0c;或者…

“轻松实现文件夹批量重命名:使用顺序编号批量改名“

你是否曾经遇到过需要批量重命名文件夹&#xff0c;却因为繁琐的手动操作而感到困扰&#xff1f;现在&#xff0c;我们为你带来了一款全新的工具——轻松实现文件夹批量重命名&#xff0c;使用顺序编号批量改名。这款工具将帮助你轻松解决文件夹重命名的问题&#xff0c;提高工…

SpringSecurity5|12.实现RememberMe 及 实现原理分析

security/day08 这个功能大家还熟悉么&#xff1f;我们在登录网站的时候&#xff0c;除了让你输入用户名和密码&#xff0c;还会有个勾选框&#xff1a; 记住我&#xff01;&#xff01;&#xff01;不是让大家记住我哈。 值得一提的是&#xff0c;Spring Security 也提供了这个…

2023年汉字小达人市级比赛在线模拟题更新:40分钟150题完整对标

今天是2023年11月19日&#xff0c;距离11月30日的汉字小达人市级比赛还有11天。许多孩子正在利用难得的周末抓紧练习和备赛。 结合一些孩子的反馈和需求&#xff0c;我把150题的在线模拟题做了更新&#xff0c;增加了前面的个人信息填写的部分&#xff0c;并且把整个试卷的完成…

python自动化标注工具+自定义目标P图替换+深度学习大模型(代码+教程+告别手动标注)

省流建议 本文针对以下需求&#xff1a; 想自动化标注一些目标不再想使用yolo想在目标检测/语意分割有所建树计算机视觉项目想玩一玩大模型了解自动化工具了解最前沿模型自定义目标P图替换… 确定好需求&#xff0c;那么我们发车&#xff01; 实现功能与结果 该模型将首先…

python——第九天

今日目标&#xff1a; 偏函数 递归 字符串对象 切片 常见排序和查找 偏函数&#xff1a; python中存在一种函数的特殊使用&#xff0c;称为偏函数 如果在调用某个函数时&#xff0c;恰好某一个或者&#xff0c;某一些参数都是一个固定值&#xff08;正好不是默认值&#xff09;…

Linux常用命令——bye命令

在线Linux命令查询工具 bye 命令用于中断FTP连线并结束程序。。 补充说明 bye命令在ftp模式下&#xff0c;输入bye即可中断目前的连线作业&#xff0c;并结束ftp的执行。 语法 bye实例 bye在线Linux命令查询工具
最新文章