Java 中Lock接口锁的使用

news2024/9/22 7:26:20

一. 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.coloradmin.cn/o/2154545.html

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

相关文章

从零开始学习TinyWebServer

写在前面 项目参考&#xff1a;https://github.com/qinguoyi/TinyWebServer 写作框架/图参考&#xff1a;https://blog.csdn.net/qq_52313711/article/details/136356042?spm1001.2014.3001.5502 原本计划是&#xff0c;先将项目代码大概看一遍&#xff0c;然后再着手实现一下…

【hot100-java】【组合总和】

R8-回溯篇 印象题&#xff0c;很基本的回溯 class Solution {void backtrack(List<Integer> state,int target,int[] choices,int start,List<List<Integer>> ret){//子集和等于target&#xff0c;记录解if (target0){ret.add(new ArrayList<>(state)…

LeetCode讲解篇之1343. 大小为 K 且平均值大于等于阈值的子数组数目

文章目录 题目描述题解思路题解代码 题目描述 题解思路 题目让我们求长度为k的子数组并且该子数组的平均值大于threshold&#xff0c;对于这题&#xff0c;我们可以考虑维护一个长度为k的窗口&#xff0c;窗口不断向右滑动&#xff0c;遍历所有长度为k的子数组&#xff0c;我们…

低版本SqlSugar的where条件中使用可空类型报语法错误

SQLServer数据表中有两列可空列&#xff0c;均为数值类型&#xff0c;同时在数据库中录入测试数据&#xff0c;Age和Height列均部分有值。   使用SqlSugar的DbFirst功能生成数据库表类&#xff0c;其中Age、Height属性均为可空类型。   开始使用的SqlSugar版本较低&…

win11 wsl2安装ubuntu22最快捷方法

操作系统是win11&#xff0c;wsl版本是wsl2&#xff0c;wsl应该不用多介绍了&#xff0c;就是windows上的虚拟机&#xff0c;在wsl上可以很方便的运行Linux系统&#xff0c;性能棒棒的&#xff0c;而且wsl运行的系统和win11主机之间的文件移动是无缝的&#xff0c;就是两个系统…

力扣115-不同的子序列(Java详细题解)

题目链接&#xff1a;不同的子序列 前情提要&#xff1a; 因为本人最近都来刷dp类的题目所以该题就默认用dp方法来做。 dp五部曲。 1.确定dp数组和i下标的含义。 2.确定递推公式。 3.dp初始化。 4.确定dp的遍历顺序。 5.如果没有ac打印dp数组 利于debug。 每一个dp题目…

Spring IDEA 2024 安装Lombok插件

1.简介 Lombook插件的Data标签可以自动生成类的get和set以及toString方法。 2.安装步骤 在idead设置的插件中搜索lombok插件&#xff0c;安装。 在Spring项目的pom.xml中添加依赖项 <dependency><groupId>org.projectlombok</groupId><artifactId…

数据结构与算法——Java实现 7.习题——反转链表

当你穿过了暴风雨&#xff0c;你已不是原来那个人 —— 24.9.21 206. 反转链表 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1]示例 2&#xff1a; 输…

echarts标注legend的配置

代码&#xff1a; legend: [{top: bottom, //上下位置 top center bottom 还可以用百分比50%等orient: horizontal, // 竖立 vertical horizontal 水平的// right: 0, //靠右 还可以用百分比 50%等// left: 0,// 靠左 还可以用百分比 50%等// 左右位置 否则居中itemWidth: …

前端框架Vue、React、Angular、Svelte对比

在对比 React、Vue.js、Angular 和 Svelte 时&#xff0c;除了在高层次的特性上有显著差异&#xff0c;它们在核心设计理念和底层实现机制上也有明显的不同。为了清晰地理解这些框架&#xff0c;我们可以从以下几个方面来分析它们的核心不同点和底层不同点。 1. 框架类型和设计…

ARM 栈和函数调用

阅读本文前&#xff0c;可以先阅读下述文档&#xff0c;对函数栈、栈帧等的概念会有所了解&#xff0c;会对本文章的理解大有益处 X86_64 栈和函数调用 1、调试环境 Ubuntu&#xff1a; liangjieliangjie-virtual-machine:~/Desktop$ cat /proc/version Linux version 6.5.0…

WebRTC编译后替换libwebrtc.aar时提示找不到libjingle_peerconnection_so.so库

Loading native library: jingle_peerconnection_so 问题原因&#xff1a;编译的时候只编译了armeabi-v7a的版本&#xff0c;但是应用程序是arm64-v8a&#xff0c;所以无法运行 解决方法&#xff1a;更新编译脚本&#xff0c;加上arm64-v8a进行编译 ./tools_webrtc/android/bu…

OpenAI GPT o1技术报告阅读(5)-安全性对齐以及思维链等的综合评估与思考

✨继续阅读报告&#xff1a;使用大模型来学习推理(Reason) 原文链接&#xff1a;https://openai.com/index/learning-to-reason-with-llms/ 编码 我们训练了一个模型&#xff0c;在2024年国际信息学奥林匹克竞赛&#xff08;IOI&#xff09;中得分213分&#xff0c;排名在第…

Arthas sysenv(查看JVM的环境变量)

文章目录 二、命令列表2.1 jvm相关命令2.1.5 sysenv&#xff08;查看JVM的环境变量&#xff09;举例1&#xff1a;sysenv 查看所有环境变量举例2&#xff1a;sysenv java.version 查看单个属性&#xff0c;支持通过tab补全 二、命令列表 2.1 jvm相关命令 2.1.5 sysenv&#x…

saas收银系统源码

1. 线下门店多样化收银 ①门店有社区小店、也会有大店&#xff0c;甚至还会有夫妻店&#xff0c;同时还要有Windows版和安卓版&#xff0c;需满足不同门店的收银需求。 ②支持Windows收银、安卓收银、无人自助收银、聚合码收银等&#xff0c;支持ai智能称重、收银称重一体机等…

Unity3D入门(二) :Unity3D实现视角的丝滑过渡切换

1. 前言 上篇文章&#xff0c;我们已经初步了解了Unity3D&#xff0c;并新建并运行起来了一个项目&#xff0c;使相机视角自动围绕着立方体旋转。 这篇文章&#xff0c;我们来讲一下Unity3D怎么过渡地切换视角。 我们继续是我上篇文章中的项目&#xff0c;但是需要向把Camera…

Qt Debugging帮助文档

Qt中给断点添加条件&#xff1a; 示例1&#xff1a; 当i10时&#xff0c;程序中断 但不知道为什么&#xff0c;46行的条件没有生效&#xff0c;47行的条件生效了 给断点添加忽略次数&#xff1a; 在程序停止之前忽略该断点200次。 Breakpoints (Debugging with GDB)

AI 时代的网络危机沟通计划

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

Go-知识recover

Go-知识recover 1. 介绍2. 工作机制2.1 recover 定义2.2 工作流程2.3 总结 3. 原理3.1 recover函数的真正逻辑3.2 恢复逻辑3.3 生效条件 4. 总结4.1 recover的返回值是什么&#xff1f;4.2 执行recover之后程序将从哪里继续运行&#xff1f;4.3 recover为什么一定要在defer中使…

2024年信息学奥赛CSP-J1入门组初赛真题试卷

2024年信息学奥赛CSP-J1入门组初赛真题试卷 题目总数&#xff1a;20 总分数&#xff1a;100 选择题 第 1 题 单选题 32位int类型的存储范围是&#xff08; &#xff09; A. -2147483647 ~ 2147483647 B. -2147483647 ~ 2147483648 C. -2147483648 ~ 2147483647…