java单体服务自定义锁名称工具类
需求:
操作员能够对自己权限下的用户数据进行数据填充,但是不同操作员之间可能会有重复的用户数据,为了避免操作员覆盖数据或者重复操作数据,应该在操作用户数据时加锁,要求加的这一把锁必须是细粒度的锁,不能锁住所有用户的操作 ,只锁当前操作用户,锁的名字为用户id,在获取到锁之后执行业务操作,否则阻塞等待, 这个单体服务锁工具如何设计?
思路:
思路:
- 1、锁工具类:首先创建一个ConcurrentHashMap{k(用户id)
v(锁实例)},包含获取锁方法,定时任务晚上删除map中键值对的方法(尝试获取锁,如果能够拿到锁就说明锁没有在使用) - 2、在定时任务删除某个键值对期间,去尝试获取写锁,如果能够获取到说明所以经不再被使用,直接删除map中的键值对,之后被删除的锁实例失效
- 3、其他线如果在删除锁实例之前获取到了,在执行业务的前后需要判断一下这把锁是否和当前key下的锁实例是否一致,一样执行业务,否则锁失效,抛异常,事务回滚
代码:
public class LockUtilsTest {public static final ConcurrentHashMap<String, ReentrantLock> lockMap = new ConcurrentHashMap<>();private static ScheduledExecutorService scheduledExecutorService;private static volatile boolean scheduledTaskCreated = false;/*** 获取锁实例,如果锁不存在就创建一个新的锁实例,并将这个锁实例返回* @param lockName* @return*/public static final ReentrantLock getLock(String lockName) {return lockMap.computeIfAbsent(lockName, key -> {//创建定时任务createdTask();return new ReentrantLock();});}/**** 懒加载 创建定时任务*/private static void createdTask(){System.out.println(Thread.currentThread().getName()+":尝试创建定时任务");if (!scheduledTaskCreated) {synchronized (LockUtilsTest.class) {if (!scheduledTaskCreated) {System.out.println(Thread.currentThread().getName()+":成功创建定时任务");scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();scheduledExecutorService.scheduleAtFixedRate(() -> {// 调用当前类的 clearUserLocks 方法new LockUtilsTest().clearUserLocks();}, 3, 3, TimeUnit.SECONDS);scheduledTaskCreated = true;}else{System.out.println(Thread.currentThread().getName()+":尝试创建定时任务发现定时任务已经被创建");}}}else{System.out.println(Thread.currentThread().getName()+":尝试创建定时任务发现定时任务已经被创建");}}/*** 遍历map,尝试获取锁,没获取到说明这个key的锁还在被使用,获取到就直接删除这个键值对,并且其他拥有这个被删除的锁实例的线程获取到锁也不执行业务*/private void clearUserLocks() {if (StringUtils.isNotEmpty(lockMap) && lockMap.size() >0){System.out.println(Thread.currentThread().getName()+":执行定时任务,删除未使用的锁");ArrayList<String> keys = new ArrayList<>();lockMap.forEach((key, value) -> {ReentrantLock lock = lockMap.get(key);//获取到锁之后直接删除键值对,否则锁在被使用不删除if (lock.tryLock()){try {keys.add(key);lockMap.remove(key);}finally {lock.unlock();lock = null;//取消强引用防止内存泄露}}});System.out.println(Thread.currentThread().getName()+":被删除的锁:"+keys.toString());}}/*** 极端情况下才会考虑这种删除锁时,可以使用分段锁思想,加入一百万个用户,我按照一个用户一把锁,估计会创建一百万把,但是使用分段锁之后,可以让一万个用户公用一把锁,* 这样就会减少锁的数量,还有频繁加锁和释放锁带来的开销,相比与百万数据公用一把锁改成万人用一把锁,性能是有明显提升的,锁的垃圾回收也要处理好,不要出现内存泄露*//**** 判断锁是否有效 :当定时任务删除对应键的锁时,如果有线程已经获得这个键原来的锁实例没抢到锁时,恰好另一个线程在清空键值对后获取这个key的锁实例,这个key在这一瞬间就会存在两个锁实例* 他们可以同时执行业务,彼此并不互斥,所以当获得锁之后,要再次和当前锁是否一致,只要不一致就说明这个锁实例不能再使用了,会存在线程不安全问题* @param lockName*/public static final Boolean checkLock(String lockName,ReentrantLock checkLock){ReentrantLock nowLock = lockMap.get(lockName);/**确保锁存在并且 比较两个对象是否是一个实例*/if (ObjectUtil.isNotEmpty(nowLock) && checkLock == nowLock){return true;}return false;}//使用这个方法如果有数据库操作,要加事务注解回滚数据public static final void checkLockFlag(String lockName,ReentrantLock checkLock){ReentrantLock nowLock = lockMap.get(lockName);/**确保锁存在并且 比较两个对象是否是一个实例*/if (ObjectUtil.isNotEmpty(nowLock) && checkLock == nowLock){}else{throw new RuntimeException("当前锁已失效");}}
}class LockUtilsTestClass{public static void main(String[] args) {//测试定时任务是否是单例懒加载创建 =》 目标 : 成功创建定时任务//taskTest();//测试一百个线程抢锁,锁阻塞是否生效 =》 目标 :执行业务出现一百次 后缀是同一把锁//lockTest();//测试锁被删除后,已经获取锁实例的线程是否生效 =》 目标:老锁返回友好提示 五次 √delLockTest();}/*** 测试定时任务是否是单例懒加载创建* 预期结果:* 尝试创建定时任务 100次* 尝试创建定时任务发现定时任务已经被创建 99次* 成功创建定时任务 1次*/static void taskTest(){for (int i = 0; i < 100; i++) {new Thread(() ->{ReentrantLock testLock = LockUtilsTest.getLock("test");}).start();}}/**** 测试锁阻塞是否生效 =》 目标 同一个锁执行业务一百次*/static void lockTest(){for (int i = 0; i < 100; i++) {new Thread(() ->{String lockName = "fe2r32r23f34t34tf34f34fd";ReentrantLock lock = LockUtilsTest.getLock(lockName);lock.lock();try {System.out.println("执行业务"+lock.toString());}finally {lock.unlock();}}).start();}}static void delLockTest(){for (int i = 0; i < 5; i++) {new Thread(() ->{String lockName = "fe2r32r23f34t34tf34f34fd";ReentrantLock lock = LockUtilsTest.getLock(lockName);try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}lock.lock();try {if (LockUtilsTest.checkLock(lockName,lock)){//执行业务System.out.println("老锁执行业务");}else{//当前线程持有锁失效,返回友好提示System.out.println("老锁返回友好提示");}}catch (Exception e){e.printStackTrace();}finally {lock.unlock();}}).start();}}
}
工具类优缺点:
-
使用ConcurrentHashMap作为锁的存储容器有以下优点:
-
线程安全:ConcurrentHashMap本身是线程安全的,多个线程可以并发地访问和修改它,而无需额外的同步。
-
高效性:ConcurrentHashMap在并发场景下通常比synchronizedMap或Hashtable等同步容器有更好的性能。
-
灵活性:你可以根据键(例如资源的唯一标识)动态地获取和释放锁,这使得代码更加灵活和可维护。
-
重用锁对象:尽可能地重用现有的锁对象,而不是为每个新的资源或操作都创建一个新的锁,如果你频繁地创建和销毁锁对象,或者维护大量的锁对象(如存储在ConcurrentHashMap中),那么这确实会增加内存消耗。每个ReentrantLock对象都会占用一定的内存空间,而且如果锁的数量非常大,那么存储这些锁的数据结构(如ConcurrentHashMap)也会占用更多的内存,当你动态地获取锁时,JVM会确保线程安全地访问锁对象,这通常涉及一些底层的同步机制,但这些机制通常不会引入大量的额外内存消耗。同样,释放锁也只是改变锁的状态,使其可以被其他线程获取,这同样是一个轻量级的操作。
-
注意问题
-
避免无限制地增长:虽然将锁存储在ConcurrentHashMap中可以限制锁对象的数量,但如果你的应用程序允许无限制地添加新的键(即资源),那么lockMap本身可能会无限制地增长,从而消耗大量内存。确保你的应用程序有适当的机制来管理资源,避免无限制地添加新的锁。
相关文章:
java单体服务自定义锁名称工具类
需求: 操作员能够对自己权限下的用户数据进行数据填充,但是不同操作员之间可能会有重复的用户数据,为了避免操作员覆盖数据或者重复操作数据,应该在操作用户数据时加锁,要求加的这一把锁必须是细粒度的锁,…...
整理好了!2024年最常见 20 道并发编程面试题(四)
上一篇地址:整理好了!2024年最常见 20 道并发编程面试题(三)-CSDN博客 七、请解释什么是条件变量(Condition Variable)以及它的用途。 条件变量是一种同步机制,用于在多线程编程中协调线程间的…...
持续交付一
一、 你的项目依赖的 jQuery 版本是 1.0.0 ,Bootstrap 依赖的版本是 1.1.0,而 Chosen 依赖的版本是 1.2.0,看上去都是小版本不一致,一开始并没有发现任何问题,但是如果到后期发现不兼容,可能就为时已晚了。…...

基于 Python 解析 XML 文件并将数据存储到 MongoDB 数据库
1. 问题背景 在软件开发中,我们经常需要处理各种格式的数据。XML 是一种常用的数据交换格式,它可以存储和传输结构化数据。很多网站会提供 XML 格式的数据接口,以便其他系统可以方便地获取数据。 我们有这样一个需求:我们需要从…...
Interview preparation--案例加密后数据的模糊查询
加密数据的模糊查询实现方案 我们知道加密后的数据对模糊查询不是很友好,本篇就针对加密数据模糊查询这个问题来展开讲一讲实现的思路,希望对大家有所启发。为了数据安全我们在开发过程中经常会对重要的数据进行加密存储,常见的有࿱…...
一个简单的R语言数据分析案例
在R语言中,数据分析可以涵盖广泛的领域,包括描述性统计、探索性数据分析、假设检验、数据可视化、机器学习等。以下是一个简单的R语言数据分析案例,该案例将涵盖数据导入、数据清洗、描述性统计、数据可视化以及一个简单的预测模型。 案例&a…...

springCloudAlibaba之分布式事务组件---seata
Seata Sea学习分布式事务Seata二阶段提交协议AT模式TCC模式 Seata服务搭建Seata Server(事务协调者TC)环境搭建seata服务搭建-db数据源seata服务搭建-nacos启动seata服务 分布式事务代码搭建-client端搭建接入微服务应用 Sea学习 事务:事务是…...

无公网IP与服务器完成企业微信网页应用开发远程调试详细流程
文章目录 前言1. Windows安装Cpolar2. 创建Cpolar域名3. 创建企业微信应用4. 定义回调本地接口5. 回调和可信域名接口校验6. 设置固定Cpolar域名7. 使用固定域名校验 前言 本文主要介绍如何在企业微信开发者中心通过使用内网穿透工具提供的公网域名成功验证回调本地接口服务! …...

CSS 字体颜色渐变
CSS 字体颜色渐变 css 代码: 注意:background: linear-gradient(属性),属性可以调整方向 例如:to bottom 上下结构,to right 左右结构font-family: DIN, DIN;font-weight: normal;font-size: 22px;color:…...
【机器学习】基于CTC模型的语音转换可编辑文本研究
1.引言 1.1语音识别技术的研究背景 1.1.1.语音识别技术的需求 语音识别技术的研究和发展,对于提升人类与机器的交互方式具有深远的影响。首先,它极大地提高了工作效率和便利性。通过语音指令控制设备,用户可以更快捷地完成任务,…...

数据结构错题答案汇总
王道学习 第一章 绪论 1.1 3.A 数据的逻辑结构是从面向实际问题的角度出发的,只采用抽象表达方式,独立于存储结构,数据的存储方式有多种不同的选择;而数据的存储结构是逻辑结构在计算机上的映射,它不能独立于逻辑结构而存在。数…...

搞AI?中小企业拿什么和大厂拼?
近期,苹果发布M4芯片,号称“比当今任何AI PC的任何神经引擎都强!”紧随其后微软携“CopilotPCs”的概念加入AI PC激战。截至目前,包括联想、惠普、华为等多家主流PC厂商在内,已经至少推出了超50款AI PC产品。 AI重塑行…...

光伏电站阵列式冲击波声压光伏驱鸟器
光伏电站内鸟群的聚集可不是一件好事,鸟类排泄物,因其粘度大、具有腐蚀性的特点,一旦堆积在太阳能板上,会严重影响光伏电站的发电效率。长期积累的鸟粪不仅难以清洗,还可能引发组件的热斑效应,严重时甚至可…...
Webrtc支持FFMPEG硬解码之解码实现(三)
前言 此系列文章分分为三篇, Webrtc支持FFMPEG硬解码之Intel(一)-CSDN博客 Webrtc支持FFMPEG硬解码之NVIDA(二)-CSDN博客 Webrtc支持FFMPEG硬解码之解码实现(三)-CSDN博客 AMD硬解目前还没找到可用解码器,欢迎留言交流 环境 Windows平台 VS2019 <...
RIP协议
RIP基本概念 RIP(Routing Information Protocol)是一种基于距离矢量的路由协议,用于在自治系统(AS)内的网关之间交换路由信息。RIP 是一种相对简单且广泛使用的内部网关协议(IGP),适…...
计算机视觉与深度学习实战,Python为工具,基于光流场的车流量计数应用
一、引言 随着科技的飞速发展,计算机视觉和深度学习技术在现代社会中的应用越来越广泛。其中,车流量计数作为智能交通系统的重要组成部分,对于城市交通管理和规划具有重要意义。本文旨在探讨以Python为工具,基于光流场的车流量计数应用,为智能交通系统的发展提供技术支撑。…...
插入排序(排序算法)
文章目录 插入排序详细代码 插入排序 插入排序,类似于扑克牌的玩法一样,在有序的数组中,扫描无序的数组,逐一的将元素插入到有序的数组中。 实现细节: 从第一个元素开始,该元素可以认为已经被排序取出下…...
【附带源码】机械臂MoveIt2极简教程(六)、第三个demo -机械臂的避障规划
系列文章目录 【附带源码】机械臂MoveIt2极简教程(一)、moveit2安装 【附带源码】机械臂MoveIt2极简教程(二)、move_group交互 【附带源码】机械臂MoveIt2极简教程(三)、URDF/SRDF介绍 【附带源码】机械臂MoveIt2极简教程(四)、第一个入门demo 【附带源码】机械臂Move…...

innovus:route secondary pg pin
我正在「拾陆楼」和朋友们讨论有趣的话题,你⼀起来吧? 拾陆楼知识星球入口 innovus route secondary pg pin分以下几步: #设置pg net连接 globalNetConnect VDD_AON -type pgpin -pin VNW #设置ndr rule,具体绕线层次跟signal绕…...
btstack协议栈实战篇--LE Peripheral - Test Pairing Methods
btstack协议栈---总目录_bt stack是什么-CSDN博客 目录 1.Main Application Setup 2.Packet Handler 3.btstack_main 4.log信息 首先先理解一下,ble中的central,Peripheral,master和slave的理解? 以下是对这些概念的理解: “Central”(中心设备):与“Maste…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...

Ubuntu Cursor升级成v1.0
0. 当前版本低 使用当前 Cursor v0.50时 GitHub Copilot Chat 打不开,快捷键也不好用,当看到 Cursor 升级后,还是蛮高兴的 1. 下载 Cursor 下载地址:https://www.cursor.com/cn/downloads 点击下载 Linux (x64) ,…...
Oracle11g安装包
Oracle 11g安装包 适用于windows系统,64位 下载路径 oracle 11g 安装包...

高端性能封装正在突破性能壁垒,其芯片集成技术助力人工智能革命。
2024 年,高端封装市场规模为 80 亿美元,预计到 2030 年将超过 280 亿美元,2024-2030 年复合年增长率为 23%。 细分到各个终端市场,最大的高端性能封装市场是“电信和基础设施”,2024 年该市场创造了超过 67% 的收入。…...
41道Django高频题整理(附答案背诵版)
解释一下 Django 和 Tornado 的关系? Django和Tornado都是Python的web框架,但它们的设计哲学和应用场景有所不同。 Django是一个高级的Python Web框架,鼓励快速开发和干净、实用的设计。它遵循MVC设计,并强调代码复用。Django有…...

【PX4飞控】mavros gps相关话题分析,经纬度海拔获取方法,卫星数锁定状态获取方法
使用 ROS1-Noetic 和 mavros v1.20.1, 携带经纬度海拔的话题主要有三个: /mavros/global_position/raw/fix/mavros/gpsstatus/gps1/raw/mavros/global_position/global 查看 mavros 源码,来分析他们的发布过程。发现前两个话题都对应了同一…...

spring boot使用HttpServletResponse实现sse后端流式输出消息
1.以前只是看过SSE的相关文章,没有具体实践,这次接入AI大模型使用到了流式输出,涉及到给前端流式返回,所以记录一下。 2.resp要设置为text/event-stream resp.setContentType("text/event-stream"); resp.setCharacter…...
【Java基础】向上转型(Upcasting)和向下转型(Downcasting)
在面向对象编程中,转型(Casting) 是指改变对象的引用类型,主要涉及 继承关系 和 多态。 向上转型(Upcasting) ⬆️ 定义 将 子类对象 赋值给 父类引用(自动完成,无需强制转换&…...