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

相关推荐
维天说25 分钟前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json
Zane19941 小时前
并发 vs 并行:别再傻傻分不清了,一文讲透 Java 并发编程的第一课
java·后端
噢,我明白了2 小时前
java中Excel的导入和导出(EasyExcel)
java·开发语言·excel
吠品2 小时前
Zabbix Web界面误报Server未运行的排查与解决
java·服务器·数据库
啊湘2 小时前
天气查询API接口 按月Token鉴权 实时天气 物联网可用 文档齐全
java·后端·struts
Java内核笔记2 小时前
告别十亿美元的错误 : Spring Boot 4 空安全 (JSpecify) 实战
java·spring boot·后端
糖果店的幽灵3 小时前
langgraph的 MessagesState 解读
java·开发语言·人工智能·windows·langgraph
极光代码工作室4 小时前
基于SpringBoot的在线博客系统
java·springboot·web开发·后端开发
我是唐青枫4 小时前
Java Spring Security 实战详解:从登录认证到 JWT 权限控制
java·spring
ihuyigui4 小时前
海外签收通知短信接口
android·java·开发语言·前端·数据库·后端