设计一个利用事务特性可以阻塞线程的排他锁,并且通过注解和 AOP 来实现

设计思路:
利用数据库表记录锁标识:通过唯一标识符(如方法名 + 参数),我们可以在数据库中插入一条记录,表示当前方法正在执行。这条记录需要记录插入时间。
注解:通过注解标识哪些方法需要加锁,我们可以动态生成唯一标识符。
AOP:使用 AOP 切面处理方法调用逻辑,确保在进入目标方法之前判断是否已经有其他线程持有锁。如果有,则阻塞当前线程,直到锁被释放。
事务:利用事务的隔离性,保证在同一时刻,只有一个线程能够执行某个方法,其他线程需要等待。
超时检测:如果在某个时间点锁没有被释放(例如,锁存在时间超过10分钟),则认为发生了死锁,自动清理相关记录。
步骤及代码实现

  1. 数据库表设计
    首先,我们需要一个数据库表来存储锁的状态。表的结构大致如下:
CREATE TABLE method_lock (id BIGINT AUTO_INCREMENT PRIMARY KEY,lock_key VARCHAR(255) NOT NULL, -- 锁的唯一标识(方法名+参数)created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 锁的创建时间updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- 最后更新时间status VARCHAR(50) DEFAULT 'LOCKED', -- 锁的状态,LOCKED 表示锁定,RELEASED 表示释放expired BOOLEAN DEFAULT FALSE -- 是否过期,10分钟未释放的锁算死锁
);
  1. 创建锁的注解
    接下来,我们定义一个注解来标识需要加锁的方法。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodLock {String value(); // 锁的标识(如方法名 + 参数的唯一标识符)
}
  1. 实现 AOP 切面
    在切面中,我们要通过 @Around 来拦截带有 @MethodLock 注解的方法,获取方法的唯一标识,并在数据库中插入或查询锁的状态。
@Aspect
@Component
public class MethodLockAspect {@Autowiredprivate LockRepository lockRepository; // 用于查询和操作数据库的 repository@Around("@annotation(methodLock)") // 拦截带有 @MethodLock 注解的方法public Object around(ProceedingJoinPoint joinPoint, MethodLock methodLock) throws Throwable {String lockKey = methodLock.value() + getMethodSignature(joinPoint); // 生成锁的唯一标识符// 获取当前时间戳long currentTime = System.currentTimeMillis();// 尝试获取锁if (tryAcquireLock(lockKey, currentTime)) {try {// 如果获取到锁,则执行目标方法return joinPoint.proceed();} finally {// 目标方法执行完毕后释放锁releaseLock(lockKey);}} else {// 如果无法获取到锁,则阻塞当前线程或者抛出异常throw new RuntimeException("Unable to acquire lock, try again later.");}}private boolean tryAcquireLock(String lockKey, long currentTime) {// 查询数据库中是否存在该锁记录LockEntity lockEntity = lockRepository.findByLockKey(lockKey);if (lockEntity == null) {// 如果不存在锁记录,则插入新的锁记录LockEntity newLockEntity = new LockEntity();newLockEntity.setLockKey(lockKey);newLockEntity.setCreatedTime(new Timestamp(currentTime));lockRepository.save(newLockEntity);return true;} else {// 如果锁存在,检查锁是否已经过期(超过10分钟)long lockAge = currentTime - lockEntity.getCreatedTime().getTime();if (lockAge > 10 * 60 * 1000) {// 如果锁超时超过10分钟,认为是死锁,清理并重新获取锁lockRepository.deleteByLockKey(lockKey); // 删除死锁记录LockEntity newLockEntity = new LockEntity();newLockEntity.setLockKey(lockKey);newLockEntity.setCreatedTime(new Timestamp(currentTime));lockRepository.save(newLockEntity);return true;}// 如果锁没有过期,则阻塞当前线程return false;}}private void releaseLock(String lockKey) {// 释放锁:删除数据库中的锁记录lockRepository.deleteByLockKey(lockKey);}private String getMethodSignature(ProceedingJoinPoint joinPoint) {// 获取方法签名(例如方法名 + 参数)String methodName = joinPoint.getSignature().getName();Object[] args = joinPoint.getArgs();StringBuilder signature = new StringBuilder(methodName);for (Object arg : args) {signature.append("-").append(arg != null ? arg.toString() : "null");}return signature.toString();}
}
  1. LockRepository 示例
    假设使用的是 Spring Data JPA 来进行数据库操作,我们可以定义一个简单的 Repository 来操作 method_lock 表。
public interface LockRepository extends JpaRepository<LockEntity, Long> {LockEntity findByLockKey(String lockKey);void deleteByLockKey(String lockKey);
}
  1. LockEntity 实体类
    LockEntity 对应数据库中的 method_lock 表:
@Entity
@Table(name = "method_lock")
public class LockEntity {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String lockKey;@Column(name = "created_time")private Timestamp createdTime;@Column(name = "updated_time")private Timestamp updatedTime;private String status;private Boolean expired;// Getters and Setters
}
  1. 使用示例
    现在,我们可以在需要加锁的方法上使用 @MethodLock 注解,示例如下:
@Service
public class MyService {@MethodLock("uniqueLockKey")@Transactionalpublic void executeTask(String param) {// 执行需要加锁的逻辑System.out.println("Executing task with param: " + param);}
}

关键点总结:
锁的唯一标识:通过方法名和参数生成一个唯一的标识符 lockKey。
数据库表记录锁:通过表记录锁的状态,保证只有一个线程能获取到锁。
AOP 和注解结合:使用 AOP 和注解对方法进行拦截,判断是否已经有其他线程在执行,若没有则获取锁,若有则阻塞或抛出异常。
事务特性:保证同一时刻,只有一个线程能够执行目标方法,其他线程需要等待,。
死锁处理:在锁存在超过 10 分钟时认为是死锁,进行清理。
通过这种方式,可以实现基于事务的排他锁,确保多个线程访问同一资源时进行排队执行

解释:
例如,多个接口调用一个方法,要求多个线程调用该方法时,这些线程排队执行,该方法使用注解,aop切面,通过该注解上的特定字符串和方法名,组成唯一标识,将该标识插入表中,并且记录插入时间,并且该标识是唯一的,当有线程调用该方法时,先组装唯一标识,查询表中是否有该标识的数据,并判断是否时间距离现在是否超过了10分钟,如果超过10分钟就是死锁了,直接删除该标识的所有记录,如果没有查到就插入一条记录,如果该方法没有执行完,就在一个事务里面,该事务没有结束,当有其他方法调用该方法时,就回去查询并插入,由于前一个事务未结束,导致当前唯一标识一致,卡在插入数据时,从而达到阻塞的目的,最后方法结束后将该数据删除。

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

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

相关文章

了解模2除法:原理与应用

模2除法&#xff0c;也被称为二进制除法或XOR除法&#xff0c;是一种在二进制数制下进行的特殊除法运算。与常规的十进制或其他进制的除法不同&#xff0c;模2除法使用异或&#xff08;XOR&#xff09;运算代替减法&#xff0c;并且不涉及进位或借位。这种除法运算在数字通信、…

基于 SSH 的任务调度系统

文末附有完整项目代码 在当今科技飞速发展的时代&#xff0c;任务调度系统的重要性日益凸显。本文将详细介绍一个基于 SSH&#xff08;SpringStruts2Hibernate&#xff09;的任务调度系统的设计与实现。 一、系统概述 本系统旨在改变传统人工任务调度方式&#xff0c;通过计算…

RFC 793

读 TCP 协议 RFC-793_rfc 793-CSDN博客TCP灌包中RTT时延与RTO超时关系 - konglingbin - 博客园 TCP的RTT算法 从前面的TCP重传机制我们知道Timeout的设置对于重传非常重要。 设长了&#xff0c;重发就慢&#xff0c;丢了老半天才重发&#xff0c;没有效率&#xff0c;性能差&…

Transformer:深度学习的变革力量

深度学习领域的发展日新月异&#xff0c;在自然语言处理&#xff08;NLP&#xff09;、计算机视觉等领域取得了巨大突破。然而&#xff0c;早期的循环神经网络&#xff08;RNN&#xff09;在处理长序列时面临着梯度消失、并行计算能力不足等瓶颈。而 Transformer 的横空出世&am…

计算机网络 笔记 数据链路层 2

1,信道划分&#xff1a; (1)时分复用TDM 将时间等分为“TDM帧”&#xff0c;每个TDM帧内部等分为m个时隙&#xff0c;m个用户对应m个时隙 缺点&#xff1a;每个节点只分到了总带宽的1/m,如果有部分的1节点不发出数据&#xff0c;那么就会在这个时间信道被闲置&#xff0c;利用…

vue el-table 数据变化后,高度渲染问题

场景&#xff1a;el-table设置了height属性&#xff0c;但是切换查询条件后再次点击查询重新获取data时&#xff0c;el-table渲染的高度会有问题&#xff0c;滚动区域变矮了。 解决办法&#xff1a;使用doLayout方法‌&#xff0c;在表格数据渲染后调用doLayout方法可以重新布局…

深度学习|表示学习|一个神经元可以干什么|02

如是我闻&#xff1a; 如果我们只有一个神经元&#xff08;即一个单一的线性或非线性函数&#xff09;&#xff0c;仍然可以完成一些简单的任务。以下是一个神经元可以实现的功能和应用&#xff1a; 1. 实现简单的线性分类 输入&#xff1a;一组特征向量 x x x 输出&#xff…

开源生成式物理引擎Genesis,可模拟世界万物

这是生成大模型时代 —— 它们能生成文本、图像、音频、视频、3D 对象…… 而如果将所有这些组合到一起&#xff0c;我们可能会得到一个世界&#xff01; 现在&#xff0c;不管是 LeCun 正在探索的世界模型&#xff0c;还是李飞飞想要攻克的空间智能&#xff0c;又或是其他研究…

使用Docker模拟PX4固件的无人机用于辅助地面站开发

前言 最近在制作鸿蒙无人机地面站&#xff0c;模仿的是QGroundControl&#xff0c;协议使用mavlink&#xff0c;记录一下本地模拟mavlink协议通过tcp/udp发送 废话不多说直接上命令 1.启动docker的桌面端 启动之后才能使用docker命令来创建容器 docker run --rm -it jonas…

深度学习张量的秩、轴和形状

深度学习张量的秩、轴和形状 秩、轴和形状是在深度学习中我们最关心的张量属性。 秩轴形状 秩、轴和形状是在深度学习中开始使用张量时我们最关心的三个属性。这些概念相互建立&#xff0c;从秩开始&#xff0c;然后是轴&#xff0c;最后构建到形状&#xff0c;所以请注意这…

【json】

JSON JSON是一种轻量级的,按照指定的格式去组织和封装数据的数据交互格式。 本质上是一个带有特定格式的字符串(py打印json时认定为str类型) 在各个编程语言中流通的数据格式&#xff0c;负责不同编程语言中的数据传递和交互,类似于计算机普通话 python与json关系及相互转换…

基于 Python 自动化接口测试(踩坑与实践)

文档&#xff1a;基于 Python 的自动化接口测试 目录 背景问题描述与解决思路核心代码修改点及其详细解释最终测试结果后续优化建议 1. 问题背景 本项目旨在使用 Python 模拟浏览器的请求行为&#xff0c;测试文章分页接口的可用性。测试目标接口如下&#xff1a; bashcoder…

k8s dashboard离线部署步骤

确定k8s版本&#xff0c;以1.23为例。 部署metrics-server服务&#xff0c;最好用v0.5.2。 用v0.6.0&#xff0c;可能会报以下错误&#xff1a; nodekubemaster:~/Desktop/metric$ kubectl top nodes Error from server (ServiceUnavailable): the server is currently unabl…

python学opencv|读取图像(二十八)使用cv2.warpAffine()函数平移图像

【1】引言 前序已经对图像操作进行了广泛的学习&#xff0c;包括读取、放大缩小&#xff0c;改变BGR通道值等&#xff0c;相关链接包括且不限于&#xff1a; python学opencv|读取图像-CSDN博客 python学opencv|读取图像&#xff08;三&#xff09;放大和缩小图像_python(1)使…

【LeetCode】力扣刷题热题100道(11-15题)附源码 环形链表 二叉树中序遍历 插入法(C++)

目录 1.字母异位词分组 2.环形链表 3.环形链表2 4.二叉树的中序遍历 5.搜索插入位置 1.字母异位词分组 给你一个字符串数组&#xff0c;请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。字母异位词 是由重新排列源单词的所有字母得到的一个新单词。 排序字符…

Java一个简单的反弹动画练习

文章目录 说明代码详解创建窗体代码创建绘图板创建线程 运行结果完整代码 说明 做了一个小球和星型做反弹动画的窗体作为练习&#xff0c;分享给大家&#xff0c;为了方便和我一样的小白可以看的比较明白&#xff0c;所以尽量详细的标注了注释&#xff0c;希望能帮到同样在学习…

监控观测数据标签体系的应用实践

前言 在复杂的应用系统环境下&#xff0c;监控数据量呈现出海量且繁杂的特点&#xff0c;如何高效地对这些监控数据进行管理、分析以及从中挖掘出有价值的信息&#xff0c;成为保障系统稳定运行和优化性能的关键所在。数据标签体系的建立就变得十分重要&#xff0c;它能够为监…

Vue中el-tree结合vuedraggable实现跨组件元素拖拽

实现效果&#xff1a; 左侧el-tree: <template><el-treeclass"filter-tree":data"treeData":props"defaultProps":filter-node-method"filterNode"node-key"id"draggable:allow-drop"allowDrop"node-dr…

PHP民宿酒店预订系统小程序源码

&#x1f3e1;民宿酒店预订系统 基于ThinkPHPuniappuView框架精心构建的多门店民宿酒店预订管理系统&#xff0c;能够迅速为您搭建起专属的、功能全面且操作便捷的民宿酒店预订小程序。 该系统不仅涵盖了预订、退房、WIFI连接、用户反馈、周边信息展示等核心功能&#xff0c;更…

点击底部的 tabBar 属于 wx.switchTab 跳转方式,目标页面的 onLoad 不会触发(除非是第一次加载)

文章目录 1. tabBar 的跳转方式2. tabBar 跳转的特点3. 你的配置分析4. 生命周期触发情况5. 总结 很多人不明白什么是第一次加载&#xff0c;两种情况讨论&#xff0c;第一种情况假设我是开发者&#xff0c;第一次加载就是指点击微信开发者工具上边的编译按钮&#xff0c;每点击…