JUC之ReentrantLock

2023-11-18

一、背景

随着java内卷越来越厉害,校招经常会问一些源码知识。例如Synchronized的实现原理,ReentrantLock的实现原理,AQS的实现原理,ConCurrentHashMap的实现原理等等。如何能够灵活的应对呢?解决方案一:背八股;解决方案二:看视频;解决方案三:看源码。

二、看源码

怎么看?

​描述一下我之前看的方法哈,按着command我点,我点,我点点点,似乎看着很牛逼,从上帝视角到达了平民视角,但是点完了之后,啥玩意也不知道,只记得皮毛。今天面试的时候被问到了ReentrantLock的实现原理,我索性回答了CAS+volatile双重组合。volatile修饰了一个int类型的state,在进行更新状态的时候,使用CAS完成更新。具体公平锁和非公平锁如何实现的,脑海一片浆糊。接下来也就有了今天看ReentrantLock的源码的理由!这次思路完全不一样!我打算仿照ReentrantLock自己重新Copy一下代码。

三、Copy并非真正的Copy

如果条件允许的话,那么就左边ReentrantLock的源码,右边ReentrantLock的高仿代码,这样看起来比较方便。看到左侧代码一大坨注释,心难免有些失落。如果把八股背好的话,建议直接copy,如果没有的话,建议先把八股看完再copy。

四、高仿ReentrantLock
4.1 实现Lock接口

​ 管他三七二十一呢?先实现Lock接口

//1.实现Lock接口
public class ReentrantLockCopy implements Lock {}

实现Lock接口的所有方法

​ 注意,这里的实现不是真的去实现,我们先别急着实现,先别让程序报错!

4.2 仿照Sync继承AQS写 抽象 静态内部类Sync
//1.实现Lock接口
public class ReentrantLockCopy implements Lock {
    //2.编写抽象静态内部类Sync
   abstract static class Sync extends AbstractQueuedSynchronizer{
   }
}

重写AQS的方法

//1.实现Lock接口
public class ReentrantLockCopy implements Lock {
    //2.编写抽象静态内部类Sync
   abstract static class Sync extends AbstractQueuedSynchronizer{

       @Override
       protected boolean tryAcquire(int arg) {
           return super.tryAcquire(arg);
       }

       @Override
       protected boolean tryRelease(int arg) {
           return super.tryRelease(arg);
       }

       @Override
       protected int tryAcquireShared(int arg) {
           return super.tryAcquireShared(arg);
       }

       @Override
       protected boolean tryReleaseShared(int arg) {
           return super.tryReleaseShared(arg);
       }

       @Override
       protected boolean isHeldExclusively() {
           return super.isHeldExclusively();
       }
   }
    @Override
    public void lock() {

    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public void unlock() {

    }

    @Override
    public Condition newCondition() {
        return null;
    }
}
4.3 修改tryAcquire方法名称

​ 将方法名修改为nonfairTryAcquire,同时删除@Override注解,修改参数名称为acquires,方法修饰符为final boolean

//1.实现Lock接口
public class ReentrantLockCopy implements Lock {
    //2.编写抽象静态内部类Sync
   abstract static class Sync extends AbstractQueuedSynchronizer{
        /**
         *
         * @param acquires
         * @return
         */
        final boolean nonfairTryAcquire(int acquires) {
           return super.tryAcquire(acquires);
       }

​ nonfairTryAcquire这个方法是尝试进行加锁的意思。尝试加锁:

第一步:我们需要获取当前的线程。

第二步:获取state的状态。

第三步:判断当前是否加锁,如果state为0表示未被加锁,如果非0则需要接下来的判断->当前的锁是否为可重入锁。

第四步:如果当前未被加锁,则使用CAS完成state状态的更新,即target是否于expect相同,如果相同则更改为update的值。同时需要设置当前加锁的线程为当前的线程。

第五步:判断当前是否加了可重入锁。即一个线程多次加锁。如何判断呢?就是判断加锁的线程是否和当前线程相同,相同的话,需要获取state的状态变量,同时将传递的参数acquires加上,作为新的state状态值。如果当前新的状态值超过了最大的可重入次数,那么不好意思,抛出错误,至于为啥可能是负数,首先我们应该明确的是int的范围为(-231-1,231-1),重入的多了,肯定会溢出。如果为非负数的话,更新state状态,返回true。

第六步:返回false。

代码如下

/**
 * 非公平锁尝试加锁
 *
 * @param acquires
 * @return
 */
final boolean nonfairTryAcquire(int acquires) {
    //获取当前尝试加锁的线程
    final Thread current = Thread.currentThread();
    //获取当前state的状态
    int c = getState();
    //如果当前的状态为无锁状态,即c为0,使用cas进行加锁
    if (c == 0) {
        if (compareAndSetState(0, 1)) {
            //设置当前锁的拥有者为当前线程
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //如果当前锁为可重入锁,即加锁的线程为当前的线程,完成state的加1操作
    else if (current == getExclusiveOwnerThread()) {
        int nextc = getState() + acquires;
        if (nextc < 0) {
            throw new Error("可重入锁加的太多了,出现溢出了");
        }
        setState(nextc);
        return true;
    }
    //如果加锁失败,并且非可重入锁,则加锁失败
    return false;
}
4.4 编写锁释放tryRelease方法

​锁释放需要先获取当前的state的状态值,然后判断当前占据锁的线程和当前线程是否一致,如果不一致,抛出IllegalMonitorStateException异常。接下来需要判断当前的state是否为0,如果为0的话,则先释放锁。否则的话,当前是可重入锁,将state状态值更新,返回释放失败。

​ 代码如下:

   protected final boolean tryRelease(int releases) {
            //获取当前state的状态
            int c = getState() - releases;
            //判断加锁的线程是否为当前的线程
            if (!isHeldExclusively()) {
                throw new IllegalMonitorStateException("当前线程不是锁的持有者");
            }
            //如果当前的state的状态为0的话,即可以释放锁
            boolean free = false;
            if (c == 0) {
                free = true;
                //设置当前加锁的线程为null
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
4.5 完成其他的方法重写。
/**
 * 判断当前线程是否为锁的拥有者
 *
 * @return
 */
@Override
protected final boolean isHeldExclusively() {
    return getExclusiveOwnerThread() == Thread.currentThread();
}

/**
 * 我也不知道干啥
 *
 * @return
 */
final ConditionObject newCondition() {
    return new ConditionObject();
}

/**
 * 通过state即可获取当前锁的持有者,有人拿着就不是0,否则为0
 *
 * @return
 */
final Thread getOwner() {
    return getState() == 0 ? null : getExclusiveOwnerThread();
}

final boolean isLocked() {
    return getState() != 0;
}

/**
 * 获取当前线程可重入锁的加锁次数
 *
 * @return
 */
final int getHoldCount() {
    return isHeldExclusively() ? getState() : 0;
}

/**
 * 从流中恢复实例(即反序列化)。
 * 它指的是将序列化的数据转换回原始对象形式的过程。
 * 换句话说,它是指将序列化的数据转换为原始对象的过程。
 *
 * @param s 输入流
 */
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
    s.defaultReadObject();
    setState(0); // reset to unlocked state
}
4.6 整体代码 自行看ReentrantLock吧!
4.7 编写非公平锁的逻辑

​第一步是继承copy的Sync抽象静态内部类。

	static final class NoFairSync extends Sync {}

​其次编写加锁逻辑。注意这里是使用CAS完成了加锁,如果当前的state的状态值为0的话,加锁成功,否则的话,调用AQS为我们提供的acquire方法。

final void lock() {
    if (compareAndSetState(0, 1)) {
        //加锁成功使用CAS设置当前锁的拥有者
        setExclusiveOwnerThread(Thread.currentThread());
    } else {
        acquire(1);
    }
}

​ AQS中acquire的代码。(AQS这个代码等着下次分析AQS再作解读)

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

​最后编写tryAcquire尝试加锁的逻辑,主要还是调用我们之前copy的nonfairTryAcquire方法。

//调用抽出的公共内部类完成尝试加锁操作
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
4.8 编写公平锁的逻辑

​第一步也是继承copy的Sync抽象静态内部类。

static final class FairSync extends Sync {}

​其次编写lock方法。**注意:**这里公平锁直接调用了AQS提供的acquire方法。而非先使用CAS判断当前是否加锁,设置当前的线程为加锁的线程;否则使用acquire方法。

 @Override
        final void lock() {
            acquire(1);
        }

​尝试加锁的过程只有先判断AQS中的双向链表构成的队列前继节点是否为空。其余都一样!这也是公平锁与非公平锁的差异!

//尝试加锁
protected final boolean tryAcquire(int acquire) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        //注意这里和非公平锁的区别!如果CLH队列中存在节点,则将当前的节点入队
        if (!hasQueuedPredecessors() && compareAndSetState(0, acquire)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    } else if (Thread.currentThread() == getExclusiveOwnerThread()) {
        int nextc = acquire + c;
        if (nextc < 0) {
            throw new Error("重入次数太多");
        }
        setState(nextc);
        return true;
    }
    return false;
}
4.9 剩下的方法代码,仿照ReentranLock Copy即可
4.10 编写ReentrantLockCopy的构造方法

​ 第一个构造方法:ReentrantLock默认使用非公平锁!

public ReentrantLockRewrite() {
    sync = new NoFairSync();
}

​ 第二个构造方法:如果使用公平锁,构造方法的参数为true!

public ReentrantLockRewrite(boolean flag) {
    sync = flag ? new FairSync() : new NoFairSync();
}
五、结语

其实ReentrantLock的源码也没多少,大家要静下心来慢慢看就行了,用心灵感悟大师的奥秘!

六、常见的面试题

1.ReentrantLock公平锁和非公平锁的实现原理,以及区别!

2.ReentrantLock如何实现可重入锁!

3.ReentrantLock 实现可重入锁的时候为何需要判断state小于0的条件?

4.ReentrantLock 可中断锁的实现原理!

5.ReentrantLock与Synchronized的区别!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JUC之ReentrantLock 的相关文章

  • Logback 上下文选择器的实际使用

    Logback 的文档测井分离 http logback qos ch manual loggingSeparation html表明我可以使用上下文选择器 http logback qos ch manual contextSelecto
  • Java 9 中 java.se 模块的意义是什么?

    为什么 java 9 模块系统有 java se 模块 它对其他模块具有传递依赖关系 这与 Java 9 之前的世界中依赖整个 rt jar 不一样吗 module java se requires transitive java desk
  • setSize() 不起作用?

    我有一个程序 需要两个按钮 一个是常规按钮 另一个具有根据鼠标悬停而变化的图片 目前 由于图片很大 JButton自定义也很大 我可以更改自定义的大小并保持图像 和翻转图像 成比例吗 我尝试过 setSize 但它没有任何作用 对于任何反馈
  • spring-data-neo4j 基本一对多关系不持久

    EDIT 示例项目可在github https github com troig neo4jCustomRepository 我在后端项目中使用 Neo4J Rest 图形数据库 托管在 grapheneDb 中 和 Spring Data
  • 如何生成源代码来创建我正在调试的对象?

    我的典型场景 我处理的遗留代码有一个错误 只有生产中的客户端才会遇到 我附加了一个调试器并找出如何重现该问题their系统给定their输入 但是 我还不知道为什么会发生错误 现在我想在本地系统上编写一个自动化测试来尝试重现然后修复错误 最
  • 在java中查找OSX的版本

    我需要测试 java 中 osx 的版本是否 Try System getProperty os name and or System getProperty os version 它返回字符串 HERE https docs oracle
  • Struts ActionForm 属性应该是什么类型?

    我使用 Struts 1 2 4 继承了这个巨大的遗留 Java Web 应用程序 我有一个关于 ActionForms 的具体问题 其中一些仅具有字符串属性 即使对于数字 其中一些使用看似合适的类型 整数 日期 字符串等 这里的最佳实践是
  • 如何将 java ArrayList 转换为等效的 double[] [重复]

    这个问题在这里已经有答案了 可能的重复 如何在 Java 中从 List 转换为 double https stackoverflow com questions 6018267 how to cast from listdouble to
  • json文件格式的升级路径

    我们将 Java 应用程序的用户首选项存储在 JSON 文件中 使用Jackson http jackson codehaus org 随着我们继续开发该应用程序 我们将添加首选项 重命名首选项并删除过时的首选项 当用户将应用程序升级到下一
  • 处理大数据表时应该如何使用Hibernate Mapping

    问题定义 我有一个包含大量数据 超过 100 000 行 的数据库表 表结构如下 AppID DocID DocStatus 1 100 0 1 101 1 2 200 0 2 300 1 每个 applicationID 可能有数千个文档
  • 如何制作一个向用户显示图像而不是文本的下拉列表?

    ObjectChoiceField 字段满足我的所有要求 但它并不漂亮 这就是我所拥有的 String pets Dog Cat Duck ObjectChoiceField dd new ObjectChoiceField My Pet
  • Spring Boot 1.4:Liquibase完成后的执行方法

    我有一个基于 Spring Boot 1 4 0 的项目 该项目使用 Liquibase liquibase 完成后是否可以执行方法 像 Bean 后处理器之类的东西 我想要做的是当应用程序在开发模式下启动时向我的数据库添加一些数据 在开发
  • 调整 Java 类以提高 CPU 缓存友好性

    在设计java类时 对于实现CPU缓存友好性有哪些建议 到目前为止我学到的是应该尽可能多地使用 POD 即 int 而不是整数 这样 在分配包含对象时 数据将被连续分配 例如 class Local private int data0 pr
  • 从 API Explorer 调用 API 方法时不允许使用范围

    我在 Google App Engine 中有一个奇怪的行为 我正在使用 Eclipse 和 Java 进行开发 特别是使用 Google Cloud Endpoints 我使用以下设置创建了一个示例 API 实际上 我正在使用许多其他示波
  • AWS Lambda 和 S3 - 上传的 pdf 文件为空/损坏

    我有一个 Spring 应用程序 在 AWS Lambda 上运行 它获取文件并将其上传到 AWS S3 Spring控制器发送一个MultipartFile到我的方法 使用 Amazon API Gateway 将其上传到 AWS S3
  • 为什么 writeObject 抛出 java.io.NotSerializedException 以及如何修复它?

    我有这个异常 我不明白为什么会抛出它 或者我应该如何处理它 try os writeObject element catch IOException e e printStackTrace Where element is a Transf
  • 如何使用 Java 1.4 和 SAX 将任意数据编码为 XML?

    我们使用 SAX 来解析 XML 因为它不需要将整个 XML 文档读入内存来解析单个值 我读过很多文章 坚持认为 SAX 只能用于解析 解码 XML 而不能创建它 这是真的 不 这不是真的 您可以使用类似于以下内容的方式将 XML 编码为
  • Spring Boot 和安全性以及自定义 AngularJS 登录页面

    我正在为 Spring Security 实现一个自定义 AngularJS 登录页面 但遇到身份验证问题 遵循本教程 示例 以及他们的示例在本地运行良好 https github com dsyer spring security ang
  • 在java中加密字符串,在node.js中解密,错误:解密失败

    我正在尝试用 java 加密一个字符串 将其发送到我的 node js 服务器 然后解密 但是 当我尝试执行此操作时 尝试解密时会不断出现错误 Java加密 String privateKey someprivatekey String d
  • Spring Data JPA 存储库,具有规范、分页和标准 fetch-join

    我正在使用具有规范和分页功能的 Spring Data JPA 存储库实现实体列表的搜索 过滤服务 我正在尝试减少查询数量 n 1 问题 并使用条件获取机制获取嵌套数据 我有两个实体类 Entity Table name delegatio

随机推荐

  • PyCharm安装教程最新版(社区版)

    1 官网下载地址 PyCharm the Python IDE for Professional Developers by JetBrains 2 安装 直接Install进行安装 最后点击finish即可 3 新建项目并测试 新建一个项
  • 线程安全分析

    1 成员变量和静态变量是否线程安全 如果它们没有被共享 则线程安全 如果它们被共享了 根据它们的状态是否能够改变 又分两种情况 如果只有读操作 则线程安全 如果有读写操作 则这段代码是临界区 需要考虑线程安全 2 局部变量是否线程安全 局部
  • 编译工具Make

    文章目录 make指令 指定目标 隐藏指令 通配符 伪目标 多目标 Makefile的命令 变量 变量的基础 赋值变量 函数调用 字符串操作函数 文件名操作函数 循环函数 条件判断函数 条件判断语句 隐式规则 隐式规则举例 隐式规则中的变量
  • Linux性能监控 -- vmstat命令

    文章目录 示例 字段说明 示例 输入vmstat命令后 第一个参数表示每1秒获取一次服务器资源 第二个参数表示总共获取10次 若第二个参数不设置 则表示持续获取服务器资源 字段说明 数据项 含义 r 表示有多少任务需要CPU执行 通常与后5
  • Button与ImageButton的点击监听事件-onClick

    Button与ImageButton自身都有一个onClick点击事件 通过自身的 setOnClickListener OnClickListener 的方法添加点击事件 所有控件都有一个OnClick事件 通过点击事件的监听可以实现点击
  • 吐血总结《Mysql从入门到入魔》,图文并茂

    文章目录 1 数据库操作 1 1显示数据库 1 2 创建数据库 1 3 使用数据库 1 4 查看当前数据库 1 5 删除数据库 2 表操作 2 1 创建表 2 2 更新表 2 2 1 添加列 2 2 2 删除列 2 3 查看表结构 2 4
  • CTF基本赛制与题型

    CTF简介 CTF的全称为Capture The Flag 即夺旗赛 CTF竞赛活动蓬勃发展 已成为了锻炼信息安全技术 展现安全能力和水平的绝佳平台 CTF号称计算机界的奥林匹克 CTF目标 CTF参赛队伍的目标为获取尽可能多的flag 参
  • 随机抽样一致性算法(RANSAC)示例及源代码

    作者 王先荣 大约在两年前翻译了 随机抽样一致性算法RANSAC 在文章的最后承诺写该算法的C 示例程序 可惜光阴似箭 转眼许久才写出来 实在抱歉 本文将使用随机抽样一致性算法来来检测直线和圆 并提供源代码下载 一 RANSAC检测流程 在
  • maven死活下载不了jar包的问题

    对应 问题6 选择适合自己的更新方式 通过排除法排除过的问题 1 idea版本问题 换了4个版本都有问题 2 电脑问题 差点重装 3 网络问题 换了手机网络一样不行 4 仓库 xml配置 或者maven插件版本的问题 试了很多版本 包括id
  • Spring Boot整合fastjson

    SpringBoot在构建RESTful风格的web服务时 默认使用的是Jackson作为JSON解析器 个人使用比较习惯的 json 框架是 fastjson 所以 spring boot 默认的 json 使用起来就很陌生了 所以很自然
  • Dynamics CRM: 使用setFilterXml来过滤需要在表单的Subgrid控件中进行展示的记录

    我们经常在表单中会用到subgrid控件 通常在一个表单中插入subgrid用来显示另外一个实体中的记录 而显示的内容我们是通过定义不同的视图来进行显示的 也可以通过javascript来进行一些控制 今天这篇博文我们使用另外一种方法来实现
  • KDUpdater 入门 (Qt5)

    KDTools 2 3 0 是KDAB公司的一个Qt4工具包 采用商业 GPL LGPL 三重授权 http www kdab com kdab products kd tools 该工具包中包含一个KDUpdater的组件 为Qt程序的自
  • t-SNE降维算法详解(附matlab代码)

    什么是t SNE t SNE的主要用途是可视化和探索高维数据 它由Laurens van der Maatens和Geoffrey Hinton在JMLR第九卷 2008年 中开发并出版 t SNE的主要目标是将多维数据集转换为低维数据集
  • 计算机视觉目标检测流程

    个人接触机器视觉的时间不长 对于机器学习在目标检测的大体的框架和过程有了一个初步的了解 不知道对不对 如有错误 请各位大牛不吝指点 目标的检测大体框架 目标检测分为以下几个步骤 1 训练分类器所需训练样本的创建 训练样本包括正样本和负样本
  • 线程池面试题

    线程池面试题 1 Executor 框架三大组成部分 2 ThreadPoolExecutor 类 线程池执行器 核心 2 1 ThreadPoolExecutor 3 个最重要的参数 2 2 ThreadPoolExecutor 饱和策略
  • 电机四象限运行

    电机四象限模式 前言 电机单象限模式 电机四象限模式 电机控制中的两象限和四象限程序 两象限程序 四象限程序 前言 在主机厂工作中常常提到四象限和两象限程序 对于电驱运行方式的四象限运行不是很熟悉 学习一下 做下笔记 以防止遗忘 个人理解
  • 成功解决 XXX--1.0-SNAPSHOT.jar中没有主清单属性

    问题描述 在运行使用maven打包的项目之后 运行项目发现主类没有找到 提示XXX 1 0 SNAPSHOT jar中没有主清单属性 问题原因 maven在打包时没有配置主类 解决方案 如果您使用的是Springboot框架 那么只需要在p
  • elasticsearch7.8.0 win指定jdk版本

    修改 elasticsearch env bat 文件 set JAVA HOME D elasticsearch 7 8 0 jdk if JAVA HOME set JAVA ES HOME jdk bin java exe set J
  • Python学习6.1类与对象

    一 类的定义 1 类的组成 类属性 实例方法 静态方法 类方法 eg 输入 class Student native place 云南 类属性 def init self name age self name name self age a
  • JUC之ReentrantLock

    一 背景 随着java内卷越来越厉害 校招经常会问一些源码知识 例如Synchronized的实现原理 ReentrantLock的实现原理 AQS的实现原理 ConCurrentHashMap的实现原理等等 如何能够灵活的应对呢 解决方案