锁与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)异常处理只针对业务逻辑,不会误释放锁

相关推荐
Circ.2 小时前
SpringBoot 实现文件上传与下载(完整源码 + 详细教程)
java·spring boot·后端
zzqssliu3 小时前
Spring Boot + XXL-JOB 搭建淘宝代购系统任务调度中心
java·spring boot·后端
一行代码一行诗++3 小时前
goto语句
java·开发语言·算法
m0_639310793 小时前
大数据技术原理-HDFS的安装与应用
java·大数据·jvm·hadoop·spring·hdfs·eclipse
Plastic garden3 小时前
Redis(2) redis的高可用
java·数据库·redis
XiYang-DING3 小时前
【Spring】SpringIoC&DI
java·spring·log4j
xixingzhe23 小时前
spring构造函数注入对比@Resource
java·后端·spring
宋哥转AI4 小时前
Java搭RAG实战(三):检索问答全链路,从架构分层到SSE流式
java·agent
测试员周周4 小时前
【Appium 系列】第17节-XMind用例转换 — 从思维导图到 YAML
java·服务器·人工智能·单元测试·appium·测试用例·xmind