JVM本地锁(二)ReentrantLock可重入锁源码解析

news2024/9/25 1:25:02

什么是可重入锁呢
顾名思义,就是可以重复进入的锁,学过操作系统或者计组的可参照理解pv,或者多重中断。

demo1(){
        lock(); //第一次锁
        demo2(){
            lock(); // 第二次锁
            unlock(); 
        }
        unlock();
    }

文章目录

  • ReentrantLock
    • lock 加锁
      • 1. ReentrantLock.lock()
      • 2. sync.lock()
      • 3. unfairSync.lock()
      • 4. AQS.acquire(1)
    • unlock 解锁
      • 1. ReentrantLock.unlock()
      • 2. AQS.release(1)
      • 3. syn.tryRelease(1)
  • 总结

ReentrantLock

lock 加锁

在这里插入图片描述

1. ReentrantLock.lock()

直接从lock()入手翻阅源码

public void lock() {
        sync.lock();
    }

它调用的是sync.lock();

2. sync.lock()

在这里插入图片描述
在ReentrantLock初始化时,默认是非公平锁,有参构造true则是公平锁

非公平锁:来个线程就先试试能不能插队,不能插队才去后面排队
公平锁:线程都乖乖去后面排队去,不准插队

总而言之,sync就是个内部非公平/公平锁。

再往下看,由于lock()是抽象方法,而sync默认是非公平锁。
在这里插入图片描述

调用unfairSync.lock()

3. unfairSync.lock()

final void lock() {
		// 若CAS抢到锁,记录设置当前线程
       if (compareAndSetState(0, 1))
          setExclusiveOwnerThread(Thread.currentThread());
       else
       // 若没抢到锁
           acquire(1);
}

点击compareAndSetState

在这里插入图片描述
这就是CAS获取锁,unsafe是JDK中用于硬件实现CAS的操作。
不用管它,只需要理解这里就是CAS操作。

若state==0,则更新为1,并且设置好排他线程。即该线程成功抢到锁

那如果没抢到锁呢

4. AQS.acquire(1)

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

先执行tryAcquire(),可以理解为再次抢锁。

  • 如果成功,返回为true,再加个!,成了false,后面就不用执行
  • 如果失败,则执行后面acquireQueued(),即进入等待队列

这里nonfairSync重写方法,直接调用nonfairSync.tryAcquire(1)

在这里插入图片描述
继续往下调用nonfairTryAcquire(1)

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            // 如果state=0,再重新尝试一下看能不能抢到锁
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 如果state不为0
            // 如果这个线程就是之前已经抢到锁的那个,它又要加锁,重入!
            else if (current == getExclusiveOwnerThread()) {
            	// state递加上去
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

简单理解就是

  • 如果state==0,再重新CAS抢救一下,看能不能抢到锁,抢到了那就成功
  • 如果state不为0,说明已经被抢了。但是如果那个抢到锁的线程是自己,自己又重入了,那state+1,再次加锁,成功。
  • 如果那个抢到锁的不是自己,加锁失败。

如果加锁失败,则方法返回到AQS.acquire(1),还需要执行if后面的判断。
简单看下addWaiter();

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

这里维护着一个双向链表,简单来说就是把结点放到链表的尾部,并且更新尾结点。
加锁失败了,把这个线程放在表尾,乖乖排队吧。

这就是加锁的所有过程。

unlock 解锁

1. ReentrantLock.unlock()

在这里插入图片描述
很明显,看来这里是将之前的state一个个减1减回来

2. AQS.release(1)

public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

tryRelease也就是解锁了

3. syn.tryRelease(1)

在这里插入图片描述
就是将state-1,进行解锁操作。由于可能被可重入了,state-1后不一定为0;如果为0则将记录的线程清空。

解锁很好理解,就不详细赘述了。

总结

lock:

  • CAS获取锁,若没有线程占用锁(state==0),加锁成功并记录当前线程是有锁线程(两次,开始一次,acquire()中又一次)
  • 若state值不为0,说明锁已经被占用,则判断当前线程是否是有锁线程,若是则重入(state+1)
  • 否则加锁失败,入队等待

unlock:

  • 判断当前线程是否是有锁线程,不是则抛出异常
  • state-1, 若-1后state值为0则解锁成功,返回true
  • 若-1后state不为0,则返回false

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

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

相关文章

1990-2022年6月上市公司高管信息数据

1990-2022年6月上市公司高管信息数据、董监高信息数据 1、时间&#xff1a;1990-2022年6月 2、指标&#xff1a;证券代码、统计截止日期、人员ID、姓名、国籍、籍贯、籍贯所在地区代码、出生地、出生地所在地区代码、性别、年龄、毕业院校、学历、专业、职称、个人简历、是否…

给数组创建视图(浅拷贝)修改视图值影响原数组值修改视图形状不影响原数组形状numpy.view()

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 给数组创建视图&#xff08;浅拷贝&#xff09; 修改视图值影响原数组值 修改视图形状不影响原数组形状 numpy.view() 对于以下python代码表述错误的一项是? import numpy as np import nu…

代码随想录二刷day5 两数之和 四数相加 (三数之和 四数之和) ->多写几遍(解法双指针放缩)

二刷复习 文章目录二刷复习哈希表和哈希法unordered 和 ordered 的不同242.有效字母的异位词349.两个数组的交集202.快乐数两数之和四数相加2383.赎金信三数之和&#xff08;这道题需要重复做&#xff0c; 双指针&#xff09;四数之和哈希表和哈希法 哈希表&#xff1a;这是两…

BGP建邻实验

目录 1.拓扑图 2.要求 3.实验思路 4.主要配置 5.测试 6.实验总结 1.拓扑图 2.要求 每台路由器都有两个环回&#xff0c;一个24的环回&#xff0c;一个32的环回&#xff1b;32的环回用于建邻&#xff0c;24的环回用于用户网段&#xff0c;最终实现所有24的环回可以ping通即…

BeyondCorp 打造得物零信任安全架构

1. 背景 当前&#xff0c;大部分企业都使用防火墙 (firewall) 来加强网络边界安全。然而&#xff0c;这种安全模型是有缺陷&#xff0c;因为当该边界被破坏&#xff0c;攻击者可以相对容易地访问公司的特权内部网。 边界安全模型通常被比作中世纪城堡&#xff1a;城墙厚厚的堡…

Mysql 报“Finished with error”,该怎么及解决?

用了多年的Mysql,当用navicat导库时&#xff0c;偶尔会遇到“Finished with error”错误&#xff0c; 如下图&#xff1a; 下面是我结合工作经验&#xff0c;总结一下&#xff0c;将相应情况及解决方法提供给网友们&#xff1a; 情况1&#xff1a;导入的sql数据库脚本文件中日…

vue3 antd项目实战——Form表单的重置【使用resetFields()重置form表单数据】

vue3 ant design vue项目实战——Form表单【resetFields重置form表单数据】关于form表单的文章场景复现resetFields()重置表单数据项目实战关于form表单的文章 文章内容文章链接Form表单提交和校验https://blog.csdn.net/XSL_HR/article/details/128495087?spm1001.2014.3001…

剑指offer----C语言版----第五天

目录 1. 重建二叉树 1.1 题目描述 1.2 复习基础知识 1.3 思路分析 1.4 总结 1. 重建二叉树 原题链接&#xff1a; 剑指 Offer 07. 重建二叉树 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/zhong-jian-er-cha-shu-lcof/submissions/ 1.1 题目描述…

极简四则运算解释器

前言: 这是最近完成的一个小的 demo&#xff0c;一个极简四则运算解释器。前面&#xff0c;已经基于这个想法发了两篇博客了&#xff1a; 四则运算和二叉树 简单四则运算语法树可视化 然后&#xff0c;前两天也就完成了这个总体的 demo 程序。本来整个程序的思路大致上有了&…

前端框架 Nuxt3 集成axios 配置跨域

目录 一、安装axios 二、Nuxt3项目集成axios 1、项目根目录下创建server/api目录 2、调用封装的单例axios对象 3、页面中调用请求函数 刚开始通过Nuxt3使用axios时&#xff0c;以为axios还需要配置跨域&#xff0c;但经过多次测试发现&#xff0c;在Nuxt3框架里并不需要配…

磨金石教育摄影技能干货分享|胡杨为什么被新疆人奉为精神图腾

痴迷于胡杨的摄影家 新疆摄影师王汉冰&#xff0c;昨天我们介绍了他一张《沙狐之眼.》&#xff0c;天人合一的画面让我们感到震撼。 除此之外王汉冰还有一个称号那就是“胡杨王”。 意思很明确&#xff0c;那就是擅长拍摄胡杨&#xff0c;作品也多是以胡杨见长。 新疆地大物博&…

【OpenCV 例程 300篇】253. 多帧图像(动图)的读取与保存

『youcans 的 OpenCV 例程300篇 - 总目录』 【youcans 的 OpenCV 例程 300篇】253. 多帧图像&#xff08;动图&#xff09;的读取与保存 1. 多帧图像&#xff08;动图&#xff09; 多帧图像是将多幅图像或帧数据保存在单个文件中&#xff0c;也称为多页图像或图像序列&#xf…

eNSP 实现静态路由中的路由备份

eNSP 实现静态路由中的路由备份 eNSP 实现静态路由中的路由备份&#xff1b;路由备份功能&#xff0c;可以提高网络的可靠性。用户可以根据实际情况&#xff0c;配置到同一目的地的多条路由&#xff0c;其中一条路由的优先级最高&#xff0c;作为主路由&#xff0c;其余的路由…

06 retrieveFileStream 之后需要调用 completePendingCommand 否则业务代码会存在问题

前言 问题是这样的 之前 同事碰到了这样的一个问题, 说是基于 ftp 客户端更新文件名字 更新失败 然后 看了一下, 原来是 调用了 retrieveFileStream, 然后 没有同步等待 数据传输完成, 然后 之后直接调用了 rename 的方法 然后 发现 rename 返回的是 false, 并且 文件名…

看我今年奋斗,观我未来之路

看我今年奋斗&#xff0c;观我未来之路 。 博客之星评选已经进行了几天。我也没有很去关注这东西&#xff0c;毕竟一个刚注册一年、写了无数水文的误导他人的博主&#xff0c;怎么可能拿到博客之星&#xff1f; 我&#xff0c;只是无聊时转发一下&#xff0c;那句毫无新意、从…

大学生如何在网上赚零花钱,适合学生党可做的零花钱项目

大学生的课余时间是非常多的&#xff0c;利用这些时间&#xff0c;我们可以去做点小兼职赚点零花钱&#xff0c;既可以补贴生活费&#xff0c;又可以获得不一样的生活体验。 现在大学生完全可以不用去大街上发传单&#xff0c;或者去咖啡店当服务员了&#xff0c;自己在网上就…

【数据结构】单链表(线性表)的实现

目录 一、什么是链表 二、单链表的实现 1、动态申请一个结点 2、单链表打印 3、单链表尾插 4、单链表的尾删 5、单链表的头插 6、单链表头删 7、单链表查找 8、单链表在pos位置之后插入x 9、单链表删除pos位置之后的值 10、单链表在pos位置之前插入x 11、单链表删除pos位置的值…

前端期末考试试题及参考答案(04)

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 一、 填空题 在页面中&#xff0c; ______标签用于创建一个表单。< form>中的______属性用于指定接收并处理表单数据的服务器url地址。< form>中的______表示以…

基于springboot+Vue的疫情防控系统(程序+数据库)

大家好✌&#xff01;我是CZ淡陌。一名专注以理论为基础实战为主的技术博主&#xff0c;将再这里为大家分享优质的实战项目&#xff0c;本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#xff0c;希望你能有所收获&#xff0c;少走一些弯路…

Linux 资源限制 setrlimit

有的时候为了避免程序毫无意义的占用CPU&#xff08;如死循环&#xff09;、过度占用内存&#xff08;如内存泄漏&#xff09;&#xff0c;我们可以限制程序使用的资源。 下面主要从两个角度限制资源&#xff1a; 限制程序累计运行时长限制可以使用的内存大小 限制资源使用到…