锁与try catch的位置引发的思考

文章目录

前言

在测试一个接口的时候,使用jmeter进行压测的时候,出现了多次调用成功的情景。

开始的时候以为是redis中的锁的问题或者是业务逻辑处理的问题,但是走查了一遍代码后,排除了。经过一通分析,发现是锁的获取和释放的位置出现了问题。

修改后也就在此做个记录~

问题代码

java 复制代码
try{
	boolean lockFlagRaise= JedisUtils.acquireLock(productListByIds.get(0).getProductId()+":createLock","1",20*60);
	if(!lockFlagRaise){
		throw new RuntimeException("该产品正在提取中,请稍后刷新再试。联系电话010-xxxxxxxx-1。");
	}
	---------业务逻辑处理,此处省略-----------
	//释放锁  
	JedisUtils.del(productListByIds.get(0).getProductId()+":createLock");
	} catch (Exception e) {
	//释放锁  
	JedisUtils.del(productListByIds.get(0).getProductId()+":createLock");
	logger.info("指令生成失败原因["+e.getMessage()+"]");
	throw new RuntimeException(e.getMessage());
}

主要框架就是上边的内容,不知道大家发现问题没有?如果跟我一样没有发现,那么跟着下边的时间线来捋一遍就明了了。

时间线 :

T1: 请求A调用 acquireLock(),成功获取锁,返回 true

T2: 请求A进入业务处理(耗时操作)

T3: 请求B调用 acquireLock(),因为锁被A持有,返回 false

❌ 注意:这里返回 false,不是抛异常

T4: 请求B进入 if(!lockFlagRaise) 分支,抛出 RuntimeException

T5: 请求B被 catch 捕获,执行 JedisUtils.del(...)

⚠️ 关键问题:请求B释放了请求A持有的锁!

T6: 请求C调用 acquireLock(),发现锁已被释放,成功获取锁

T7: 请求C进入业务处理

T8: 请求A完成业务,执行 JedisUtils.del(...)

⚠️ 此时锁可能已被C持有,A释放了C的锁

结果:多个请求同时执行业务逻辑,造成重复生成

结合代码分析一下

java 复制代码
try{
	boolean lockFlagRaise= JedisUtils.acquireLock(...);
	// 情况1:acquireLock锁竞争失败,返回false
	// 情况2:acquireLock抛出异常,如redis宕机、网络异常
	// 情况3:义务处理抛出异常
	if(!lockFlagRaise){
		// 获取锁失败,抛出的异常会被catch捕获
		throw new RuntimeException("抛出异常");
	}
} catch (Exception e) {
	// 无论是哪种异常,都会执行这里
	// 关键!!!情况1和情况2中,当前线程根本没获取到锁~ 然后就误删了其他线程持有的锁
	JedisUtils.del(...);
}

压测时可能出现的异常情况:

1、Redis连接池耗尽:大量请求同时获取连接,部分请求超时抛异常

2、网络抖动:偶发的网络超时导致 acquireLock 抛异常

3、Redis慢查询:某个请求阻塞,导致其他请求超时

修改后的代码

java 复制代码
boolean lockFlagRaise= JedisUtils.acquireLock(productListByIds.get(0).getProductId()+":createLock","1",20*60);
if(!lockFlagRaise){
	throw new RuntimeException("该产品正在提取中,请稍后刷新再试。联系电话010-xxxxxxxx-1。");
}
try{
	---------业务逻辑处理,此处省略-----------
	//释放锁  
	JedisUtils.del(productListByIds.get(0).getProductId()+":createLock");
} catch (Exception e) {
	//释放锁  
	JedisUtils.del(productListByIds.get(0).getProductId()+":createLock");
	logger.info("指令生成失败原因["+e.getMessage()+"]");
	throw new RuntimeException(e.getMessage());
}

T1: 请求A调用 acquireLock(),成功获取锁,返回 true

T2: 请求A进入业务处理

T3: 请求B调用 acquireLock(),返回 false

T4: 请求B执行 if(!lockFlagRaise),抛出 RuntimeException

✅ 注意:这里没有进入 try-catch,因为异常发生在try外部

✅ 不会执行任何 del 操作

T5: 请求A完成业务,执行 JedisUtils.del(...) 释放锁

T6: 请求C调用 acquireLock(),成功获取锁

T7: 请求C进入业务处理

结果:业务逻辑串行执行,不会重复生成

总结

1、第一段代码在压测时出现重复生成的根本原因:

(1)错误释放未持有的锁:锁获取失败的请求错误地释放了其他线程持有的锁

(2)锁失效的连锁反应:一次错误释放导致多个请求并发执行

(3)缺少锁持有者验证:没有通过唯一标识验证释放锁的合法性

2、第二段代码为什么不会:

(1)锁获取在 try-catch 外部,获取失败的请求不会进入异常处理

(2)异常处理只针对业务逻辑,不会误释放锁

相关推荐
小师兄吃牛肉8 分钟前
SQL 编辑器异常排查:基于 KStudio 的一次实战记录
java·sql·编辑器
m0_5873830010 分钟前
无人自助拼豆系统开发实战指南 核心功能架构搭建与落地经验分享
java·经验分享·spring·小程序·架构·需求分析
Wang's Blog14 分钟前
Java 项目实战: 交友项目 - Dubbo 超时与重试
java·dubbo·交友
BD_Marathon17 分钟前
ChatPromptTemplate的高级特性
android·java·javascript
要开心吖ZSH24 分钟前
定时任务是用外部调度(xxl-job)还是本地化( @Scheduled)更香?以号源刷新任务为例?
java·xxl-job·定时任务·scheduled
nagualky12325 分钟前
GPT-4 系列退役在即:一套可回滚的模型迁移验收法
java·开发语言·ai·架构
今天的砖头有点烫手啊26 分钟前
Java 8 Stream 原理拆解:惰性求值与并行流陷阱
java·jvm
starzy199033 分钟前
Flink 任务调度 Graph 源码深度剖析:从 StreamGraph 到 ExecutionGraph 的三层演进
java·大数据·flink
Eyfcom35 分钟前
〖实战〗快运系统 Oracle 性能优化:从“能用”到“扛住双11”的 Java + Spring Boot 实战
java·oracle·性能优化·快递管理系统
m0_5873830040 分钟前
碰碰好评卡功能落地全流程实战指南 附核心逻辑与常见问题解决方案
java·前端·数据库·后端·struts·需求分析