Java 中Lock接口锁的使用

目录

一. Lock接口下的实现类

1. ReentrantLock可重入锁

1.1. 可重入锁的原理

1.2. ReentrantLock的特点

1.3. 使用注意

1.4. 代码示例

2. ReentrantReadWriteLock读写锁

2.1. 实现原理

2.2. 注意事项

2.3. 代码示例


一. Lock接口下的实现类

在Java中,Lock 接口是 java.util.concurrent.locks 包中的一部分,它提供了比 synchronized 更丰富的锁操作。Lock 接口的实现类包括 ReentrantLock(可重入锁)、ReadWriteLock(读写锁) 等。

1. ReentrantLock可重入锁

ReentrantLock 是 Java 中 java.util.concurrent.locks 包下的一个可重入锁实现。可重入锁意味着一个线程可以多次获得同一把锁,而不会产生死锁。

1.1. 可重入锁的原理

  • 线程安全性:确保只有一个线程可以同时访问锁保护的资源。
  • 可重入性:同一个线程可以多次获得同一把锁,每获得一次锁,锁的持有计数器就增加一次,只有当锁的持有计数器为零时,其他线程才有机会获得这把锁。

1.2. ReentrantLock的特点

  • 公平性选择:可以选择公平锁或非公平锁。公平锁按照线程请求锁的顺序来分配锁,非公平锁则可能让等待时间较短的线程优先获得锁。
  • 条件变量支持:提供了与锁相关联的条件变量,用于实现等待/通知机制。
  • 中断处理:支持锁的中断获取,可以在获取锁的过程中响应中断。

1.3. 使用注意

  • 锁的释放:确保在 finally 块中释放锁,以避免因异常导致锁无法释放。
  • 避免死锁:即使 ReentrantLock 是可重入的,也需要小心避免死锁,例如,确保获取锁的顺序一致。
  • 性能考虑:锁操作有一定的性能开销,需要根据实际情况权衡使用锁的范围和粒度。
  • 默认创建非公平锁:如果传入true则创建的是公平锁 ReentrantLock lock = new ReentrantLock(true);

1.4. 代码示例

  • 测试代码
package com.demo.jucdemo;

import java.util.concurrent.locks.ReentrantLock;

/**
 * 文件名:ReentrantLockExample
 * 创建者:
 * 创建时间:2024-09-21
 * 描述:模拟银行 存款,取款操作
 */
public class ReentrantLockDemo {
    public static void main(String[] args) {
        //初始账户为余额为0
        AccuntInfoRL accuntInfoRL = new AccuntInfoRL("小明",0);
        //循环启动线程
        for (int i = 0; i < 10; i++) {
            //模运算来判断是存款还是取款
            if (i % 2 == 0) {
                new Thread(() -> {
                    accuntInfoRL.save(10);
                }, "存款").start();
            } else {
                new Thread(() -> {
                    accuntInfoRL.withdrawal(10);
                }, "取款").start();
            }
        }
    }
}

/**
 * 模拟测试用户
 * 提供两个方法,分别是取款,存款
 */
class AccuntInfoRL{
    private String name;
    private int asset;
    AccuntInfoRL(String name,int asset) {
        this.name = name;
        this.asset = asset;
    }

    /**
     * 创建可重入锁时,默认是非公平锁
     * new ReentrantLock(true) 传入ture时创建的是公平锁
     */
    ReentrantLock lock = new ReentrantLock();
    /**
     * 存款方法
     * @param asset
     */
    public void save(int asset) {
        //1.加锁
        lock.lock();
        try {
            //2.业务代码
            this.asset += asset;
            System.out.println(this.name+"-存款:"+asset +" || 余额:"+this.asset);
        } catch (Exception e) {
            throw new RuntimeException("存款失败");
        }finally {
            //3.释放锁
            lock.unlock();
        }
    }
    /**
     * 取款方法
     */
    public void withdrawal(int asset){
        //1.加锁
        lock.lock();
        try {
            //2.业务代码
            //先判断金额是否足够
            if(asset <= this.asset){
                this.asset -= asset;
                System.out.println(this.name+"-取款:"+asset+" || 余额:"+this.asset);
            }else {
                //如果余额不足直接打印,不做操作
                System.out.println("余额不足,取款失败");
            }
        } catch (Exception e) {
            throw new RuntimeException("取款失败");
        }finally {
            //3.释放锁
            lock.unlock();
        }
    }
}
  • 测试结果

2. ReentrantReadWriteLock读写锁

ReentrantReadWriteLock 是 Java 中实现读写锁的一个类,它支持一个读锁和一个写锁。读锁可以由多个线程同时持有,而写锁是排他的,一次只能由一个线程持有。这种锁非常适合读多写少的场景,因为它允许多个线程同时进行读操作,从而提高了程序的并发性能。

Lock 接口的读写锁是通过 ReentrantReadWriteLock 内部静态类实现的

ReentrantReadWriteLock.WriteLock.lock() // 写锁

ReentrantReadWriteLock.ReadLock.lock() // 读锁

2.1. 实现原理

  1. 锁的分离ReentrantReadWriteLock 将锁分为读锁和写锁,读锁可以由多个线程共享,写锁则是排他的。

  2. 可重入:无论是读锁还是写锁,都支持可重入性,即同一个线程可以多次获取同一把锁。

  3. 锁状态:锁的状态由一个整型变量 state 表示,其中高16位表示读锁的计数,低16位表示写锁的计数。

  4. 公平性ReentrantReadWriteLock 可以是公平的也可以是非公平的。公平锁保证线程获取锁的顺序是公平的,而非公平锁则可能让某些线程优先获取锁。

  5. 锁的获取

    • 读锁获取:如果写锁没有被占用,线程可以获取读锁,并且读锁可以被多个线程共享。
    • 写锁获取:如果读锁和写锁都没有被占用,线程可以获取写锁。
  6. 锁的释放

    • 读锁释放:释放读锁时,读锁计数减一,如果读锁计数为零,则可能允许等待的写锁获取锁。
    • 写锁释放:释放写锁时,写锁计数减一,并且唤醒等待的读锁或写锁。
  7. 锁的降级:锁降级是允许的,即线程可以先释放写锁,再获取读锁。

  8. 锁的升级:锁升级是不允许的,即线程不能先获取读锁,再获取写锁,因为这可能导致死锁。

2.2. 注意事项

  • 锁的公平性:公平锁可能会降低性能,但可以避免线程饥饿问题。
  • 锁的升级:避免在持有读锁的情况下尝试获取写锁,因为这可能导致死锁。
  • 锁的释放:确保在 finally 块中释放锁,以避免死锁。

2.3. 代码示例

  1. 创建银行账户类
    package com.demo.jucdemo;
    
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    /**
     * 文件名:AccountInfo
     * 创建者:
     * 创建时间:2024-08-22
     * 描述: 银行账户信息,存款方法,取款方法,查询账户余额方法
     */
    public class AccountInfo {
        private String name;
        private int asset;
        AccountInfo(String name, int asset) {
            this.name = name;
            this.asset = asset;
        }
        //创建读写锁
        ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
        /**
         * 存款方法,需要使用写锁
         * @param asset
         */
        public void save(int asset) {
            //1.获取写锁
            this.reentrantReadWriteLock.writeLock().lock();
            try {
                //2.业务代码
                this.asset += asset;
                System.out.println(this.name+"-存款:"+asset +" || 余额:"+this.asset);
            } catch (Exception e) {
                throw new RuntimeException("存款失败");
            }finally {
                //释放锁
                this.reentrantReadWriteLock.writeLock().unlock();
            }
        }
        /**
         * 查询账户余额方法
         * 需要使用读锁,
         * @return
         */
        public AccountInfo queryRemaining(){
            //1.获取读锁
            this.reentrantReadWriteLock.readLock().lock();
            try {
                //2.业务处理
                return new AccountInfo(this.name,this.asset);
            } catch (Exception e) {
                return null;
            }finally {
                //3.释放读锁
                this.reentrantReadWriteLock.readLock().unlock();
            }
        }
        /**
         * 取款方法
         * 账户减钱使用写锁
         */
        public void withdrawal(int asset){
            //1.获取写锁
            this.reentrantReadWriteLock.writeLock().lock();
            try {
                //2.业务处理
                //先判断金额是否足够
                if(asset <= this.asset){
                    this.asset -= asset;
                    System.out.println(this.name+"-取款:"+asset+" || 余额:"+this.asset);
                }else {
                    //如果余额不足直接打印,不做操作
                    System.out.println("余额不足,取款失败");
                }
            } catch (Exception e) {
                throw new RuntimeException("取款失败");
            }finally {
                //3.释放写锁
                this.reentrantReadWriteLock.writeLock().unlock();
            }
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public int getAsset() {
            return asset;
        }
        public void setAsset(int asset) {
            this.asset = asset;
        }
    }
    
  2. 创建测试类
    package com.demo.jucdemo;
    
    /**
     * 文件名:Main
     * 创建者:
     * 创建时间:2024-08-19
     * 描述:测试类
     */
    public class Main {
        public static void main(String[] args) {
            //初始化账户信息,金额为0
            AccountInfo account = new AccountInfo("小红", 0);
    
            //循环启动线程
            for (int i = 0; i < 10; i++) {
                //1.开启个新线程查询余额
                new Thread(() -> {
                    //取款之后查询余额
                    AccountInfo accountInfo = account.queryRemaining();
                    System.out.println("查询账户余额:"+"账户名称:"+accountInfo.getName()+" || 账户余额:"+accountInfo.getAsset());
                }, "查询余额").start();
    
                //2.模运算来判断是存款还是取款
                if (i % 2 == 0) {
                    new Thread(() -> {
                        account.save(10);
                    }, "存款").start();
                } else {
                    new Thread(() -> {
                        account.withdrawal(10);
                    }, "取款").start();
                }
            }
        }
    }
    
  3. 测试结果

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

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

相关文章

【Kubernetes】日志平台EFK+Logstash+Kafka【实战】

一&#xff0c;环境准备 &#xff08;1&#xff09;下载镜像包&#xff08;共3个&#xff09;&#xff1a; elasticsearch-7-12-1.tar.gz fluentd-containerd.tar.gz kibana-7-12-1.tar.gz &#xff08;2&#xff09;在node节点导入镜像&#xff1a; ctr -nk8s.io images i…

离散化 ---( 求区间和)

什么是离散化&#xff1f; 离散化是将连续的数值范围映射到有限的、离散的数值集合的过程。在许多情况下&#xff0c;数据可能会存在多个重复值或范围较大的连续值。为了简化处理&#xff0c;尤其是处理区间查询和增量问题时&#xff0c;我们可以将这些值转换为一组有限的、唯一…

C++ const成员函数

个人主页&#xff1a;Jason_from_China-CSDN博客 所属栏目&#xff1a;C系统性学习_Jason_from_China的博客-CSDN博客 所属栏目&#xff1a;C知识点的补充_Jason_from_China的博客-CSDN博客 C const引用常量 使用规则 引用常量对象&#xff1a;可以引用一个常量对象&#xff0…

zabbix基本概念与组件

文章目录 一、zabbix简介二、​​​​​​​zabbix构成三、​​​​​​​zabbix监控对象四、​​​​​​​zabbix常用术语五、 Zabbix 6.0 新特性1.Zabbix server高可用防止硬件故障或计划维护期的停机2.Kubernetes系统从多个维度采集指标 六、zabbix 工作原理1、主动模式2、…

基于飞桨paddle2.6.1+cuda11.7+paddleRS开发版的目标提取-道路数据集训练和预测代码

基于飞桨paddle2.6.1cuda11.7paddleRS开发版的目标提取-道路数据集训练和预测代码 预测结果&#xff1a; 预测影像&#xff1a; &#xff08;一&#xff09;准备道路数据集 下载数据集地址&#xff1a; https://aistudio.baidu.com/datasetdetail/56961 mass_road.zip …

基于SpringBoot + Vue的医院预约挂号系统(角色:用户、医生、管理员)

文章目录 前言一、详细操作演示视频二、具体实现截图三、技术栈1.前端-Vue.js2.后端-SpringBoot3.数据库-MySQL4.系统架构-B/S 四、系统测试1.系统测试概述2.系统功能测试3.系统测试结论 五、项目代码参考六、数据库代码参考七、项目论文示例结语 前言 &#x1f49b;博主介绍&a…

excel单元格增加可选下拉列表

excel单元格增加可选下拉列表 下拉设置&#xff1a;数据–数据验证-选择序列-填写来源&#xff08;来源数据用英文逗号分隔&#xff09;&#xff08;是,否&#xff09;- 区域应用&#xff1a;选定区域-数据验证-是-确认

【代码随想录训练营第42期 Day61打卡 - 图论Part11 - Floyd 算法与A * 算法

目录 一、Floyd算法与A * 算法 1、Floyd算法 思想 伪代码 2、 A * 算法 思想 伪代码 二、经典题目 题目一&#xff1a;卡码网 97. 小明逛公园 题目链接 题解&#xff1a;Floyd 算法 题目二&#xff1a;卡码网 127. 骑士的攻击 题目链接 题解&#xff1a;A * 算法&a…

Windows系统修改Tomcat虚拟机内存参数

文章目录 I 修改Tomcat虚拟机内存参数基于tomcat管理程序进行配置基于setenv文件进行配置II 查看服务器状态/manager/status 查看服务器状态manager/jmxproxy 查询Tomcat指标I 修改Tomcat虚拟机内存参数 基于tomcat管理程序进行配置 查看堆内存分配情况: jmap -heap pid jst…

列表、数组排序总结:Collections.sort()、list.sort()、list.stream().sorted()、Arrays.sort()

列表类型 一.Collections.sort() Collections.sort()用于List类型的排序&#xff0c;其提供了两个重载方法&#xff1a; 1.sort(List<T> list) &#xff08;1&#xff09;List指定泛型时只能指定引用数据类型&#xff0c;也就是说无法用于基本数据类型的排序。 &am…

wsksvg - 优化升级,支持多进程处理文件和 SVG 图像转化

前言 在不断发展的前端技术中&#xff0c;图像的优化和处理始终是提升应用性能的关键。wsksvg 插件的最新版本在之前的基础上进行了重大升级&#xff0c;现支持多进程处理文件以及将 SVG 图像转化为多种其他格式的图片。这一功能的引入不仅提升了处理效率&#xff0c;还大幅度…

MySQL之内置函数

目录 一、日期函数 二、字符串函数 三、数学函数 四、其它函数 一、日期函数 常见的日期函数如下&#xff1a; 函数名称说明current_date()获取当前日期current_time()获取当前时间current_timestamp()获取当前时间戳date_add(date, interval d_value_type)在date中添加日…

Jenkins Pipeline 中通过勾选参数来控制是否构建 Docker 镜像

1.定义参数&#xff1a; 使用 booleanParam 定义一个布尔参数&#xff0c;示例如下 booleanParam(name: BUILD_DOCKER, description: 是否构建Docker镜像, defaultValue: false)2.使用参数&#xff1a; 在 stage 中&#xff0c;根据参数的值决定构建方式&#xff1a; stage(编…

如何把用过的试卷恢复?5个软件帮助你快速进行试卷清除

如何把用过的试卷恢复&#xff1f;5个软件帮助你快速进行试卷清除 要恢复或清除用过的试卷上的答案或标记&#xff0c;通常涉及去除笔记、擦除手写痕迹或处理扫描件中的内容。以下五款软件可以帮助你有效地清除试卷上的文字、答案或标记&#xff0c;并将其恢复到空白状态&…

云原生|浅谈云原生中的对象存储之MinIO 的使用

一、什么是对象储存 对象存储&#xff08;Object Storage&#xff09;以对象的形式存储和管理数据&#xff0c;这些对象可以是任何类型的数据&#xff0c;例如 PDF&#xff0c;视频&#xff0c;音频&#xff0c;文本或其他文件类型。对象存储使用分布式存储架构&#xff0c;数据…

大型语言模型:通过代码生成、调试和 CI/CD 集成改变软件开发的游戏规则

文章目录 1. 引言2. 代码生成2.1 提高开发人员的生产力2.2 训练与适应 3. 使用人工智能进行调试和修复错误3.1 提高准确性的创新工具3.2 定制解决方案 4. 无缝 CI/CD 集成4.1 CI/CD 集成人工智能&#xff1a;可靠部署的催化剂4.2 持续学习和改进4.3 缩小开发和运营之间的差距 5…

媒界:2025河南台球及配套设施展会3月举办

立足中原&#xff0c;辐射全国&#xff0c;壹肆柒中国国际台球产业博览会3月在郑州盛大举办&#xff1b; 2025中国&#xff08;郑州&#xff09;国际台球产业博览会&#xff08;壹肆柒台球展&#xff09; The 2025 China (Zhengzhou) International Billiards Industry Expo …

数据库中间件Mycat

Mycat是基于Java编写的实现了MySQL协议的数据库中间件&#xff0c;可以将它看成一个数据库代理&#xff0c;可以直接用MySQL客户端工具访问。其核心功能是分库分表和读写分离。 MyCat 是基于阿里开源的 Cobar 产品而研发&#xff0c;Cobar 的稳定性、可靠性、优秀的架构和性能…

【JAVA开源】基于Vue和SpringBoot的学科竞赛管理系统

本文项目编号 T 047 &#xff0c;文末自助获取源码 \color{red}{T047&#xff0c;文末自助获取源码} T047&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 查…

【Java 集合】List接口 —— ArrayList 与 LinkedList 详解

List接口继承自Collection接口&#xff0c;是单列集合的一个重要分支。 在List集合中允许出现重复的元素&#xff0c;所有的元素是以一种线性方式进行存储的&#xff0c;在程序中可以通过索引&#xff08;类似于数组中的元素角标&#xff09;来访问集合中的指定元素。另外&…