手把手教你构建源码级组件——Java互斥不可重入锁

2023-05-16

文章目录

  • 构造同步组件的步骤
    • 1. 定义内部类Syn
    • 2. 继承同步器,重写指定方法
    • 3. 调用同步器方法
  • 互斥不可重入锁实现
    • 代码实现
    • 测试Demo
    • 运行结果
    • 结果分析

构造同步组件的步骤

之前的学习中我们学习了AQS的原理,其中有许多构建锁与同步器的相关概念我们需要了解到:

  • 首先同步器是实现锁的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义;
  • 锁是面向使用者的,提供锁交互的实现;
  • 同步器是面向锁的实现者,简化了锁的实现方式,屏蔽了同步状态管理、线程排队、等待/唤醒等底层操作。

从代码层面,同步器是基于模板模式实现的,可以通过AQS可重写的方法进行子类具体功能实现:

例如下面是AQS中tryAcquire模板方法的源码(如果子类没实现会怕抛出异常)

/**
* 模板方法:
*  protected关键字
*  没有任何实现
* @param arg
* @return
*/
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

那么我们在构建同步组件的时候也就是需要实现以下几步:

1. 定义内部类Syn

随后将同步器组合在自定义同步组件的实现中,即定义内部类Syn继承AQS

public class XXX implements Lock {
    public class Sync extends AbstractQueuedSynchronizer{
	}
}

2. 继承同步器,重写指定方法

之后在Syn中重写AQS方法,根据同步器需求如下挑选实现不同方法

  • tryAcquire(int arg):独占式获取同步状态;
  • tryRelease(int arg):独占式释放同步状态;
  • tryAcquireShared(int arg):共享式获取同步状态,返回大于0的值表示获取成功,否则失败
  • tryReleaseShared(int arg):共享式释放锁
  • isHeldExclusively():当前线程是否在独占模式下被线程占用,一般该方法表示是否被当前线程占用

例如不可重入同步器:

public class XXX implements Lock {
	public class Sync extends AbstractQueuedSynchronizer{
	
        @Override
        protected boolean tryAcquire(int arg) {
            final Thread current = Thread.currentThread();
            if (compareAndSetState(0, 1)) {
                // 获取成功之后,当前线程是该锁的持有者,不需要再可重入数
                setExclusiveOwnerThread(current);
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            if (getState() == 0) {
                throw new IllegalMonitorStateException();
            }
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        @Override
        protected boolean isHeldExclusively() {
              return getState() == 1;
        }
        // 返回Condition,每个Condition都包含了一个队列
        Condition newCondition() {
            return new ConditionObject();
        }
    }
}

3. 调用同步器方法

最后调用同步器提供的模板方法,即同步组件类实现Lock方法之后,在lock/unlock方法中调用内部类Syn的方法acquire(int arg)等方法

public class XXX implements Lock {
    
   ........
   	private final Sync sync = new Sync();
    @Override
    public void lock() {
        sync.acquire(1);
    }
    @Override
    public void unlock() {
        sync.release(1);
    }
    ........

}

具体请看下面的实验部分

互斥不可重入锁实现

在我之前写过的博文中(详解Java锁的升级与对比(1)——锁的分类与细节(结合部分源码))介绍可重入锁与不可重入锁的区别时,就写到JUC中没有不可重入锁的具体实现,但是可以类比,现在呢,我们可以做到实现了,具体看下面代码,模式完全符合依赖Lock与AQS构造同步组件模式。

代码实现

public class Mutex implements Lock {

    private final Sync sync = new Sync();
    public class Sync extends AbstractQueuedSynchronizer{
        @Override
        protected boolean tryAcquire(int arg) {
            final Thread current = Thread.currentThread();
            if (compareAndSetState(0, 1)) {
                // 获取成功之后,当前线程是该锁的持有者,不需要再可重入数
                setExclusiveOwnerThread(current);
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            if (getState() == 0) {
                throw new IllegalMonitorStateException();
            }
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        @Override
        protected boolean isHeldExclusively() {
              return getState() == 1;
        }
        // 返回Condition,每个Condition都包含了一个队列
        Condition newCondition() {
            return new ConditionObject();
        }
    }


    @Override
    public void lock() {
        sync.acquire(1);
    }
    @Override
    public void unlock() {
        sync.release(1);
    }

    @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 Condition newCondition() {
        return null;
    }
}

其中核心代码就是重写的两个方法:

  • tryAcquire(int arg)方法:主要是设置同独占式更新同步状态,CAS实现state+1
  • tryRelease(int arg)方法:独占式释放同步状态,释放锁持有

测试Demo

package com.yyl.threadtest.utils;

import java.util.Date;

public class MutexDemo {
    public static void main(String[] args) {
        final Mutex lock = new Mutex();
        class Worker extends Thread {
            @Override
            public void run() {
                // 一直不停在获取锁
                while (true) {
                    lock.lock();
                    try {
                        System.out.println(Thread.currentThread().getName() +" hold lock, "+new Date());
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        lock.unlock();
                        System.out.println(Thread.currentThread().getName() +" release lock, "+new Date());
                    }
                }
            }

        }
        for (int i = 0; i < 10; i++) {
            Worker worker = new Worker();
            // 以守护进程运行,VM退出不影响运行,这里只是为了一个打印效果,去掉注释一直打印
            worker.setDaemon(true);
            worker.start();
        }
        // 每隔一秒换行
        for (int j = 0; j < 10; j++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println();
        }
    }
}

运行结果

image-20230223123456554

Thread-0 hold lock, Thu Feb 23 12:33:53 CST 2023

Thread-0 release lock, Thu Feb 23 12:33:54 CST 2023
Thread-1 hold lock, Thu Feb 23 12:33:54 CST 2023

Thread-1 release lock, Thu Feb 23 12:33:55 CST 2023
Thread-2 hold lock, Thu Feb 23 12:33:55 CST 2023

Thread-2 release lock, Thu Feb 23 12:33:56 CST 2023
Thread-3 hold lock, Thu Feb 23 12:33:56 CST 2023

Thread-3 release lock, Thu Feb 23 12:33:57 CST 2023
Thread-4 hold lock, Thu Feb 23 12:33:57 CST 2023

Thread-6 hold lock, Thu Feb 23 12:33:58 CST 2023
Thread-4 release lock, Thu Feb 23 12:33:58 CST 2023

Thread-6 release lock, Thu Feb 23 12:33:59 CST 2023
Thread-5 hold lock, Thu Feb 23 12:33:59 CST 2023

Thread-5 release lock, Thu Feb 23 12:34:00 CST 2023
Thread-7 hold lock, Thu Feb 23 12:34:00 CST 2023

Thread-7 release lock, Thu Feb 23 12:34:01 CST 2023
Thread-8 hold lock, Thu Feb 23 12:34:01 CST 2023

Thread-9 hold lock, Thu Feb 23 12:34:02 CST 2023
Thread-8 release lock, Thu Feb 23 12:34:02 CST 2023

Process finished with exit code 0

结果分析

互斥锁的核心就是同一个同步状态只能被一个线程持有,其它线程等待持有线程释放才能竞争获取。截图一开始的运行结果分析:

Thread-0 hold lock, Thu Feb 23 12:33:53 CST 2023

Thread-0 release lock, Thu Feb 23 12:33:54 CST 2023
Thread-1 hold lock, Thu Feb 23 12:33:54 CST 2023

Thread-1 release lock, Thu Feb 23 12:33:55 CST 2023
Thread-2 hold lock, Thu Feb 23 12:33:55 CST 2023

10个线程不断竞争锁,一开始Thread-0在08 12:33:53获取到锁,持有锁1秒后在释放12:33:54时释放,同时Thread-1立马获取到锁,1秒后于12:33:55释放锁,同时Thread-2立马获取到了锁…

根据输出结果来说,完全符合Mutex作为互斥锁这个功能:同一时刻只有一个线程持有锁(同步状态),其它线程等待释放后才能获取

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

手把手教你构建源码级组件——Java互斥不可重入锁 的相关文章

  • Eclipse 中的 Java 简单电子邮件程序

    我想制作一个简单的程序 您可以从其中发送电子邮件命令行 我找到了这个教程 http www tutorialspoint com java java sending email htm http www tutorialspoint com
  • SWIG 类型映射 uint8_t* 从 C/C++ 到 java.nio.ByteBuffer

    我正在尝试将输入和输出缓冲区从 C 传递给 java 类 出于效率原因 我需要使用 ByteBuffer 这两个缓冲区都是在 C 部分中分配的 我需要将它们传递给一个 java 函数 该函数将使用输入缓冲区进行一些计算并将结果写入输出缓冲区
  • 在游戏框架中编写功能测试的正确方法

    在为基于 play1 2 4 的 web 应用程序编写功能测试时 我对如何正确编码感到有点困惑 困惑在于所涉及的事务边界 我在某处读到每个测试都有自己的事务 在我的应用程序中 用户可以登录并向购物车添加一些商品 然后他可以提供一个地址 以便
  • 如何在 Android 中恢复我的音频?

    我必须实现用于创建具有暂停和恢复状态的音频的应用程序 当我的应用程序作为启动时音频启动 当我按下模拟器上的后退按钮时 音频音乐处于暂停状态 但是当我的活动回来时从停止状态到前台我的音频音乐未恢复 这是我的代码 public class Au
  • Java - 了解 PrintWriter 和刷新的需要

    好吧 首先我对所有代码表示歉意 但我觉得代码太多总比代码不够好 我正在制作一个简单的聊天客户端和印刷机 尤其是我正在努力解决的问题 使用现在的代码 它将与服务器类交互 并且完美地打印我想要打印的内容 但是 当我删除 writer flush
  • BigDecimal 中 Divide 方法的 Scale()

    new BigDecimal 37146555 53880000 divide new BigDecimal 1000000 scale 这返回10 但根据API divide method 返回一个 BigDecimal 其值为 这个 除
  • IntelliJ Idea,如何从控制台删除java文件目录?

    当您运行文件时 它会打开控制台窗口 并且一直在顶部显示该文件所在的目录 这非常令人恼火 因为现在 为了将其他行与目录混合分开 我必须在启动任何 System out println 命令之前使用 n C Program FILEs 我想摆脱
  • EasyMock : java.lang.IllegalStateException: 1 个匹配器预期,2 个记录

    我在使用 EasyMock 2 5 2 和 JUnit 4 8 2 通过 Eclipse 运行 时遇到问题 我已阅读此处所有类似的帖子 但尚未找到答案 我有一个包含两个测试的类 它们测试相同的方法 我正在使用匹配器 每个测试单独运行时都会通
  • 更改 Spring Web 应用程序的默认会话超时

    我必须测试一个由 spring 和 jsp 编写的 Web 应用程序 应用程序的默认会话超时为 30 分钟 我想减少会话超时 为此 我改变了web xml文件输入tomcatInstallationLocation conf 但这不起作用
  • 为什么 Casbah / Java MongoDB 驱动程序最终会出现 java.lang.IllegalArgumentException?

    我使用时看到一个奇怪的问题casbah java driver 当驱动程序尝试从 mongo 创建响应时 我不断遇到以下异常 Oct 16 2012 10 45 07 AM com mongodb DBTCPConnector MyPort
  • 未从线程接收位置数据

    我尝试使用计时器经常发送包含用户位置的短信 最初 我遇到了空指针异常 这是由于我犯了一个简单的错误 一旦解决了这个问题 一切似乎都运行良好 但是 它永远不会获取我的位置 因此 不断发送的文本显示 无法接收位置 我想问的是为什么它无法获取我的
  • 使用 Lint 和 SonarQube 分析 Android 项目

    我真的 溢出 了试图让这些东西一起工作 我按照这里的指示进行操作 http docs sonarqube org display PLUG Android Lint Plugin http docs sonarqube org displa
  • C3P0:生产中未返回的连接超时?

    参数unreturnedConnectionTimeout给定时间段后未返回的连接超时 我正在尝试决定是否应该在我的制作中使用它persistence xml 使用它的一大优点是连接池将能够从泄漏的连接中恢复 一个很大的缺点是泄漏的连接将很
  • Android 以编程方式停止 toast 通知?

    有没有办法以编程方式停止 Toast 消息 假设我有一个按钮 单击它可以滚动 toast 消息 并且在 onclick 事件中我想停止队列中的所有消息并只显示新消息 我该怎么做 我的代码的简化版本如下 代码 public class Hel
  • 错误:列“this_.phitorsionangle”必须出现在 GROUP BY 子句中或在聚合函数中使用

    我在执行 sql 查询时遇到了一些问题 我正在使用 Hibernate Criteria 来构建查询 我通过按一定间隔 binSize 舍入值然后对它们进行分组来从数据库创建一些容器 当我直接在 SQL 中使用查询尝试时 效果非常好 SEL
  • 飞碟 - html 实体未呈现

    我正在使用 Flying saucer lib 生成 pdf 但我对一些 html 实体有问题 我已经在寻找解决方案 我在这个论坛和其他地方找到了很多提示 但仍然存在问题 我尝试过这种方法 http sdtidbits blogspot c
  • 如何使用 Kafka 发送大消息(超过 15MB)?

    我发送字符串消息到Kafka V 0 8使用 Java Producer API 如果消息大小约为 15 MB 我会得到MessageSizeTooLargeException 我尝试过设置message max bytes到 40 MB
  • Hibernate 命名查询使用 Like 和 % % 运算符?

    在我的 Hibernate JPA 示例代码中 public List
  • Java:如何检测(并更改?)System.console 的编码?

    我有一个在控制台上运行的程序 其变音符号和其他特殊字符在 Mac 上以 的形式输出 这是一个简单的测试程序 public static void main String args System out println h h System
  • selenium 没有找到合适的方法,直到(ExpectedCondition)

    这是有线的问题 我导入的项目运行 100 几个月前 今天我已将其与依赖项一起导入 但存在问题WebDriverWait 这是我的代码 WebDriverWait driverWait new WebDriverWait driver 100

随机推荐

  • PINN深度学习求解微分方程系列一:求解框架

    下面我将介绍内嵌物理知识神经网络 xff08 PINN xff09 求解微分方程 首先介绍PINN基本方法 xff0c 并基于Pytorch框架实现求解一维Poisson方程 内嵌物理知识神经网络 xff08 PINN xff09 入门及相
  • PINN深度学习求解微分方程系列三:求解burger方程逆问题

    下面我将介绍内嵌物理知识神经网络 xff08 PINN xff09 求解微分方程 首先介绍PINN基本方法 xff0c 并基于Pytorch的PINN求解框架实现求解含时间项的一维burger方程逆问题 内嵌物理知识神经网络 xff08 P
  • 深度学习求解微分方程系列五:PINN求解Navier-Stokes方程正逆问题

    下面我将介绍内嵌物理知识神经网络 xff08 PINN xff09 求解微分方程 首先介绍PINN基本方法 xff0c 并基于Pytorch的PINN求解框架实现求解含时间项的二维Navier Stokes方程 内嵌物理知识神经网络 xff
  • Keras解读:使用Model()类构建网络模型的底层原理

    目录 一 前言 xff1a 二 topology py脚本简述 三 继承了Layer类的子类一般要实现 xff08 重写 xff09 以下methods xff1a 四 Node layer tensor在网络构建过程中的关系 建议结合源码
  • 【Paraview教程】第一章安装与基础介绍

    1 Paraview介绍 1 1基本介绍 ParaView是一个开源的 xff0c 跨平台的数据处理和可视化程序 ParaView用户可以迅速的建立起可视化环境利用定量或者是定性的手段去分析数据 利用它的批量处理能力可以在三维空间内在工具栏
  • 一种基于物理信息极限学习机的PDE求解方法

    作者 PINN山里娃 xff0c 作者主页 研究方向 物理信息驱动深度学习 不确定性 人工智能 偏微分方程 极限学习机 该作者聚焦深度学习模型与物理信息结合前沿研究 xff0c 提供了一系列AI for science研究进展报告及代码实现
  • js文件分片上传,断点续传

    前言 文件上传是一个老生常谈的话题了 xff0c 在文件相对比较小的情况下 xff0c 可以直接把文件转化为字节流上传到服务器 xff0c 但在文件比较大的情况下 xff0c 用普通的方式进行上传 xff0c 这可不是一个好的办法 xff0
  • JavaScript 大文件分片上传处理

    一 功能性需求与非功能性需求 要求操作便利 xff0c 一次选择多个文件和文件夹进行上传 xff1b 支持PC端全平台操作系统 xff0c Windows Linux Mac 支持文件和文件夹的批量下载 xff0c 断点续传 刷新页面后继续
  • JS实现浏览器端大文件分片上传

    IE的自带下载功能中没有断点续传功能 xff0c 要实现断点续传功能 xff0c 需要用到HTTP协议中鲜为人知的几个响应头和请求头 一 两个必要响应头Accept Ranges ETag 客户端每次提交下载请求时 xff0c 服务端都要添
  • Lua识别Jwt令牌业务

    文章目录 业务场景业务实现lua resty jwt安装令牌识别令牌测试 业务场景 如果想使用Lua识别用户令牌 xff0c 我们需要引入lua resty jwt模块 xff0c 是用于 ngx lua 和 LuaJIT 的 Lua 实现
  • WebSocket从入门到实战

    文章目录 WebSocketWebSocket 介绍WebSocket APIWebSocket 对象WebSocket属性WebSocket事件 xff1a WebSocket方法 xff1a WebSocket 实例客户端服务端代码链接
  • Error:java: 无效的源发行版: 13

    文章目录 问题原因解决 之前是英文的报错 xff0c 也是一样 xff0c 再发一遍 问题 原因 出现这个错误的原因主要是因为 JDK 版本问题 xff0c 有两个原因 xff0c 一个是编译器版本不匹配 xff0c 一个是当前项目 JDK
  • springboot项目中的bootstrap.yml配置不生效(没有自动提示)

    文章目录 问题原因及解决原因1 xff1a 原因2 xff1a 问题 xff08 1 xff09 新创建一个 springboot项目 xff0c 添加了 bootstrap yml 文件 xff0c 发现文件并没有如预期变成绿色叶子 xf
  • 人工智能——分类器性能指标之ROC曲线、AUC值

    文章目录 ROC曲线ROC曲线概念ROC曲线坐标系ROC曲线重要概念案例 xff1a 画ROC曲线 AUC值为什么使用Roc和Auc评价分类器 二分类模型预测的结果是否足够好 xff0c ROC和AUC是重要指标 ROC曲线 ROC曲线概念
  • Ubuntu18.04美化桌面(主题、图标)

    首先 xff0c 安装主题配置工具Tweaks xff0c 命令如下 xff1a sudo apt get install gnome tweak tool 之后在软件菜单中找到Tweaks图标 xff0c 打开该软件 xff0c 界面如下
  • Redis 事务支持回滚吗?

    文章目录 Redis 事务支持回滚吗 xff1f 官方解释DISCARD 命令取消事务 Redis 事务支持回滚吗 xff1f 首选 xff0c Redis 事务不支持回滚 MySQL 在执行事务时 xff0c 会提供回滚机制 xff0c
  • Docker 安装 mysql 8.0.29

    文章目录 安装拉取镜像启动容器 配置 xff08 可跳过 xff0c 建议弄上 xff09 使用启动控制台登录navicat远程登录 删除 安装 拉取镜像 span class token function docker span pull
  • SpringBoot 启动打印 Banner:佛祖保佑,永无BUG!

    最近新学了一招 打印完了 xff0c 一点bug不出 xff0c 果然 xff0c 程序的尽头是玄学 教程 在Resources目录下新建 banner txt 输入内容如下 span class token comment ooOoo s
  • 一文就懂AQS!

    文章目录 AQS介绍AQS概念AQS模式分类AQS核心思想 AQS源码结构CLH同步队列state同步状态独占式同步状态获取与释放流程图总结 xff1a 共享式同步状态获取与释放 看了很多帖子 xff0c 原理说啥的都有 xff0c 算了还
  • 手把手教你构建源码级组件——Java互斥不可重入锁

    文章目录 构造同步组件的步骤1 定义内部类Syn2 继承同步器 xff0c 重写指定方法3 调用同步器方法 互斥不可重入锁实现代码实现测试Demo运行结果结果分析 构造同步组件的步骤 之前的学习中我们学习了AQS的原理 xff0c 其中有许