java高并发加锁异常实战案例1

news2024/10/21 18:07:59

如果我们工作个三到五年,一定会在项目中遇到多线程、高并发需要加锁的场景,以及需要针对业务做异常处理;今天我以物流项目OMS中的一段代码,解析一波,有问题的请在评论区留言一起讨论

代码案例:

依赖、实体类等,我不一一列举,只讨论业务代码的逻辑和处理过程,以及用到了哪些知识点

@Resource
    private RedissonClient redissonClient;

    @Transactional(rollbackFor = Exception.class)
    public AjaxResult updateReviewStatusByIds(Map<String, Object> idsAndStatus) {
        List<String> idsList = null;
        try {
            idsList = (ArrayList<String>) idsAndStatus.get("ids");
            if (CollectionUtils.isEmpty(idsList)) {
                throw new CustomException("请选择要操作的数据!", 520);
            }

            for (String orderCode : idsList) {
                RLock lock = redissonClient.getLock("review:lock:" + orderCode);
                if (!lock.tryLock(5, 10, TimeUnit.SECONDS)) {
                    throw new CustomException("选择的订单中订单号:" + orderCode + ",被其他操作锁定,请稍等重试!");
                }
            }

            //根据入库单号集合查询
            List<OmsGodownEntry> omsGodownEntryList = omsGodownEntryMapper.selectOmsGodownEntryByIds(idsList);

            if (CollectionUtils.isEmpty(omsGodownEntryList)) {
                throw new CustomException("订单号:" + idsList + "不存在", 520);
            }

            //1=复核  不允许取消
            List<String> codes = omsGodownEntryList.stream().filter(v -> "1".equals(v.getSendBmsStatus())).map(OmsGodownEntry::getGodownEntryCode).collect(Collectors.toList());
            if (CollectionUtils.isNotEmpty(codes)) {
                throw new CustomException("以下订单状态不满足取消条件!" + codes, 520);
            }

            String createBy = SecurityUtils.getLoginUser().getUsername();
            String createUserName = SecurityUtils.getLoginUser().getUser().getNickName();
            List<OmsBasicLog> omsBasicLogs = new ArrayList<>();
            List<String> omsGodownEntries = new ArrayList<>();

            for (OmsGodownEntry omsGodownEntry : omsGodownEntryList) {
                //未复核 跳过
                if ("0".equals(omsGodownEntry.getSendBmsStatus())) {
                    continue;
                }
                OmsBasicLog omsBasicLog = new OmsBasicLog();
                omsBasicLog.setType(7);
                omsBasicLog.setCode(omsGodownEntry.getGodownEntryCode());
                omsBasicLog.setContent("订单状态改变:已取消 ——>未复核");
                omsBasicLog.setBeforeContent(JSONObject.toJSONString(idsAndStatus));
                omsBasicLog.setCreateBy(createBy);
                omsBasicLog.setCreateUserName(createUserName);
                omsBasicLogs.add(omsBasicLog);
                omsGodownEntries.add(omsGodownEntry.getGodownEntryCode());
            }
            if (CollectionUtils.isNotEmpty(omsGodownEntries)) {
                GodownEntryInfo godownEntryInfo = buildGodownEntryInfo(omsGodownEntries, "0");
                omsGodownEntryMapper.updateBmsStatusByCodeList(godownEntryInfo);
            }
            if (CollectionUtils.isNotEmpty(omsBasicLogs)) {
                omsBasicLogService.batchInsertOmsBasicLog(omsBasicLogs);
            }
            return AjaxResult.success();
        } catch (Exception e) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            logger.error("操作失败,inputParam:{},errorMsg:{}", idsAndStatus, e.getMessage(), e);
            return AjaxResult.error("操作失败,失败原因:" + e.getMessage());
        } finally {
            if (CollectionUtils.isNotEmpty(idsList)) {
                for (String orderCode : idsList) {
                    RLock lock = redissonClient.getLock("review:lock:" + orderCode);
                    if (lock != null && lock.isLocked() && lock.isHeldByCurrentThread()) {
                        lock.unlock();
                    }
                }
            }
        }
    }

代码功能解释

这段Java代码定义了一个名为 updateReviewStatusByIds 的方法,用于更新仓库入库单的审核状态。具体功能如下:
参数校验:检查传入的订单ID列表是否为空,如果为空则抛出异常。
**分布式锁:**为每个订单ID获取分布式锁,确保同一时间只有一个线程可以操作该订单。
查询订单:根据订单ID列表查询对应的仓库入库单。
状态校验:检查订单是否处于复核状态,如果是则不允许取消。
日志记录:记录订单状态变更的日志。
状态更新:更新订单的状态为“未复核”。
事务管理:使用 @Transactional 注解确保方法在事务中执行,遇到异常时回滚事务。
**异常处理:**捕获并记录异常,返回错误信息。
释放锁:在finally块中释放所有获取的锁。

控制流图

flowchart TD
A[开始] --> B{订单ID列表为空?}
B -->|Yes| C[抛出异常:请选择要操作的数据!]
B -->|No| D[获取分布式锁]
D --> E{获取锁失败?}
E -->|Yes| F[抛出异常:订单被其他操作锁定,请稍等重试!]
E -->|No| G[查询订单]
G --> H{订单不存在?}
H -->|Yes| I[抛出异常:订单号不存在]
H -->|No| J[过滤复核状态订单]
J --> K{存在复核状态订单?}
K -->|Yes| L[抛出异常:订单状态不满足取消条件!]
K -->|No| M[记录日志]
M --> N[更新订单状态]
N --> O[批量插入日志]
O --> P[返回成功]
P --> Q[释放锁]
Q --> R[结束]
F --> Q
I --> Q
L --> Q
C --> Q

> 说明

A:方法开始。
B:检查订单ID列表是否为空。
C:如果为空,抛出异常。
D:为每个订单ID获取分布式锁。
E:检查是否成功获取锁。
F:如果获取锁失败,抛出异常。
G:根据订单ID列表查询对应的仓库入库单。
H:检查查询结果是否为空。
I:如果为空,抛出异常。
J:过滤出复核状态的订单。
K:检查是否存在复核状态的订单。
L:如果存在,抛出异常。
M:记录订单状态变更的日志。
N:更新订单状态为“未复核”。
O:批量插入日志。
P:返回成功结果。
Q:释放所有获取的锁。
R:方法结束

在这段代码中,RLock 是 Redisson 客户端提供的分布式锁接口。Redisson 是一个用于 Redis 的 Java 客户端,它提供了许多高级功能,包括分布式锁、分布式集合、分布式队列等。以下是这段代码中使用的技术及其简要介绍:

技术介绍

Redisson

简介:Redisson 是一个用于 Redis 的 Java 客户端,它不仅提供了基本的 Redis
操作,还封装了许多高级功能,使其在分布式系统中更加易用。 主要功能: 分布式锁:包括公平锁、可重入锁、多锁等。 分布式集合:如
Set、List、Map 等。 分布式队列:如 BlockingQueue、Deque 等。 分布式原子操作:如
AtomicLong、AtomicDouble 等。 分布式计数器:如 CountDownLatch、Semaphore 等。 优点:
高性能:利用 Redis 的高性能特性。 易用性:提供了丰富的 API 和数据结构,简化了分布式系统的开发。 可靠性:支持 Redis
集群模式,提高了系统的可用性和可靠性。 RLock 简介:RLock 是 Redisson
提供的分布式锁接口,用于在分布式环境中实现互斥锁。 主要方法: tryLock():尝试获取锁,立即返回结果。 tryLock(long
waitTime, long leaseTime, TimeUnit
unit):尝试获取锁,等待指定时间,如果获取成功,则锁在指定时间内有效。 unlock():释放锁。 使用场景:
并发控制:在分布式系统中,多个节点可能同时访问同一资源,使用分布式锁可以确保同一时间只有一个节点可以操作该资源。
防止竞态条件:避免多个节点同时修改同一数据导致的不一致问题。 代码中的使用 在提供的代码中,RLock
用于确保对订单的修改操作是互斥的,防止多个请求同时修改同一个订单。具体使用如下:

for (String orderCode : idsList) {
    RLock lock = redissonClient.getLock("review:lock:" + orderCode);
    if (!lock.tryLock(5, 10, TimeUnit.SECONDS)) {
        throw new CustomException("选择的订单中订单号:" + orderCode + ",被其他操作锁定,请稍等重试!");
    }
}

获取锁:
通过 redissonClient.getLock 方法获取一个分布式锁实例。
尝试获取锁:使用 tryLock 方法尝试获取锁,等待最多 5 秒,如果获取成功,锁在 10 秒内有效。
释放锁:
在 finally 块中释放锁,确保即使发生异常也能释放锁。
总结
这段代码使用了 Redisson 客户端提供的 RLock 分布式锁,确保在分布式环境中对订单的修改操作是互斥的,从而避免了竞态条件和数据不一致的问题。Redisson 的高可靠性和易用性使得分布式锁的实现变得更加简单和高效。

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()

在 Spring 框架中TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 用于显式地标记当前事务为只回滚状态。这意味着无论事务后续的操作如何,最终都会回滚事务,而不会提交。
具体作用
标记事务为回滚:
当调用 setRollbackOnly() 方法后,Spring 事务管理器会将当前事务标记为只回滚状态。
即使后续的代码没有抛出异常,事务也不会被提交,而是会被回滚。
确保事务一致性:
在捕获到异常或其他错误情况时,调用 setRollbackOnly() 可以确保事务的一致性,避免部分操作成功、部分操作失败的情况。
这对于维护数据的完整性和一致性非常重要。
避免隐式回滚:
默认情况下,Spring 事务管理器会在遇到运行时异常(RuntimeException)时自动回滚事务。
但是,对于某些特定的异常(如 CheckedException),默认情况下事务不会自动回滚。在这种情况下,手动调用 setRollbackOnly() 可以确保事务回滚。
代码示例
在提供的代码中,setRollbackOnly() 被用于捕获异常并标记事务为只回滚状态:

@Transactional(rollbackFor = Exception.class)
public AjaxResult updateOmsGodownEntryOrderReviewStatusByIds(Map<String, Object> idsAndStatus) {
    try {
        // 业务逻辑
    } catch (Exception e) {
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        logger.error("操作失败,inputParam:{}, errorMsg:{}", idsAndStatus, e.getMessage(), e);
        return AjaxResult.error("操作失败,失败原因:" + e.getMessage());
    } finally {
        // 释放锁
    }
}

> 详细解释

捕获异常:
在 catch 块中捕获到任何异常时,调用 setRollbackOnly() 方法。
这样可以确保即使在 catch 块中进行了其他操作,事务仍然会被回滚。
记录日志
使用 logger.error 记录异常信息,方便后续排查问题。
返回错误信息:
返回一个包含错误信息的 AjaxResult 对象,告知客户端操作失败的原因。
总结
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 是一个非常有用的工具,用于在捕获到异常或其他错误情况时显式地标记当前事务为只回滚状态。这确保了事务的一致性和数据的完整性,避免了部分操作成功、部分操作失败的情况。

不合理或者有问题的,请大家及时纠正,谢谢!!!!!

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

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

相关文章

Java项目-基于springboot框架的医患档案管理系统项目实战(附源码+文档)

作者&#xff1a;计算机学长阿伟 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、ElementUI等&#xff0c;“文末源码”。 开发运行环境 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBoot、Vue、Mybaits Plus、ELementUI工具&#xff1a;IDEA/…

Hi3061M——VL53L0X激光测距(IIC)(同样适用于其他MCU)2

目录 前言资源下载移植基本使用IO配置调用测量 总结 前言 昨晚太晚了&#xff0c;草草结束了上一篇&#xff0c;今天更新下半部分。 昨天已经讲了VL53L0X的使用流程&#xff0c;无非就是进行6步的效准初始化&#xff0c;然后配置下模式和时间&#xff0c;开始采样&#xff0c;…

LDAP 部署手册

Centos 1. 安装openldap软件 # 安装openldap yum -y install openldap compat-openldap openldap-clients openldap-servers openldap-servers-sql openldap-devel migrationtoolscp /usr/share/openldap-servers/DB_CONFIG.example /var/lib/ldap/DB_CONFIG chown ldap:ldap…

Leetcode 跳跃游戏 二

核心任务是找出从数组的起点跳到终点所需的最小跳跃次数。 这段代码解决的是“跳跃游戏 II”&#xff08;Leetcode第45题&#xff09;&#xff0c;其核心任务是找出从数组的起点跳到终点所需的最小跳跃次数。 class Solution {public int jump(int[] nums) {//首先处理特殊情…

“智驭医疗·未来已来“:医疗保健知识中台的搭建与应用

前言 随着科技的飞速发展&#xff0c;医疗保健领域正在经历深刻的变革。知识中台作为促进医疗行业应用智能化升级的关键底座&#xff0c;正在逐渐成为提高医疗服务质量和效率的重要工具。本文将探讨医疗保健知识中台的内容构成、应用案例以及更新与维护机制。 一、医疗保健知识…

基于ASP.NET的小型超市商品管理系统

文章目录 前言项目介绍技术介绍功能介绍核心代码数据库参考 系统效果图 前言 示 文章底部名片&#xff0c;获取项目的完整演示视频&#xff0c;免费解答技术疑问 项目介绍 小型超市商品管理系统是一款针对小型超市日常运营需求设计的软件解决方案。该系统主要内容有商品类别…

【JS】无法阻止屏幕滚动

监听滚轮事件&#xff0c;阻止默认行为&#xff0c;但未生效&#xff0c;且控制台报错。 window.addEventListener(wheel, (e) > {e.preventDefault(); })这是因为现代浏览器使用 Passive 事件监听器&#xff0c;默认启用了 passive 模式以确保性能&#xff0c;不会调用 pr…

【软件安装与配置】Redis for Windows

1. 下载 Redis Redis 官方没有直接支持 Windows 的安装程序&#xff0c;但可以使用第三方的 Windows 版本。推荐使用 Memurai 或从 Microsoft archive 提供的 Redis for Windows 下载。 2. 安装 Redis 下载适合 Windows 的安装包&#xff0c;本文以Microsoft archive安装包为…

Git_IDEA集成Git

Git_IDEA集成Git 配置 Git 忽略文件 创建忽略规则文件 引用忽略配置文件 定位 Git 程序 初始化本地库 添加到暂存区 提交到本地库 切换版本 创建分支 切换分支 合并分支 解决冲突 配置 Git 忽略文件 创建忽略规则文件 引用忽略配置文件 在 .gitconfig 文件中进行&…

[Git]一文速通

概述 Git是一个分布式版本控制工具&#xff0c;主要用于管理开发过程中的源代码文件(Java类、xml文件、html页面等, )在软件开发过程中被广泛使用 Git的作用 代码回溯版本切换多人协作远程备份 通过Git 仓库来存储和管理代码 文件&#xff0c;Git 仓库分为两种: 本地仓库: 开…

C++和OpenGL实现3D游戏编程【连载15】——着色器初步

&#x1f525;C和OpenGL实现3D游戏编程【目录】 1、本节实现的内容 上一节我们介绍了通过VBO、VAO和EBO怎样将顶点发送到GPU显存&#xff0c;利用GPU与显存之间的高效处理速度&#xff0c;来提高我们的图形渲染效率。那么在此过程中&#xff0c;我们又可以通过着色器&#xff…

webstorm 编辑器配置及配置迁移

1.下载地址 WebStorm&#xff1a;JetBrains 出品的 JavaScript 和 TypeScript IDE 其他版本下载地址 2.安装 点击下一步安装&#xff0c;可根据需要是否删除已有版本 注意&#xff1a; 完成安装后需要激活 3.设置快捷键 以下为个人常用可跳过或根据需要设置 如&#xff1a…

字幕怎么自动生成?教你5种视频加字幕方法

在这个短视频时代&#xff0c;视频内容已成为传播信息、娱乐大众的重要载体。而字幕作为视频不可或缺的一部分&#xff0c;不仅能够提升观众的观看体验&#xff0c;还能跨越语言障碍&#xff0c;让所有观众都能享受视频的魅力。但怎么给视频加上字幕呢&#xff1f;下面给大家分…

vulnhub靶场之JOY

一.环境搭建 1.靶场描述 Does penetration testing spark joy? If it does, this machine is for you. This machine is full of services, full of fun, but how many ways are there to align the stars? Perhaps, just like the child in all of us, we may find joy in …

Java最全面试题->Java基础面试题->JavaSE面试题->异常面试题

异常 下边是我自己整理的面试题&#xff0c;基本已经很全面了&#xff0c;想要的可以私信我&#xff0c;我会不定期去更新思维导图 哪里不会点哪里 1.说一下Java中的异常体系&#xff1f; 2.Error和Exception的区别 Error&#xff1a;系统错误,编译时出现的错误,Exception&…

宝兰德加入华为鸿蒙生态,共谱智能运维新篇章

近日&#xff0c;华为HarmonyOS NEXT系统&#xff08;又称“纯血鸿蒙”&#xff09;正式开启公测&#xff0c;标志着国产操作系统的发展迈入了新的阶段。作为华为紧密的战略合作伙伴&#xff0c;宝兰德依托其在IT监控运维可观测性领域的技术优势&#xff0c;正式成为华为鸿蒙Ha…

RFC2616 超文本传输协议 HTTP/1.1

一、URL-俗称“网址” HTTP 使用 URL(Uniform Resource Locator&#xff0c;统一资源定位符)来定位资源&#xff0c;它是 URI(Uniform Resource Identifier&#xff0c;统一资源标识符)的子集&#xff0c;URL 在 URI 的基础上增加了定位能力 URI 除了包含 URL&#xff0c;还包…

gitee建立/取消关联仓库

目录 一、常用指令总结 二、建立关联具体操作 三、取消关联具体操作 一、常用指令总结 首先要选中要关联的文件&#xff0c;右击&#xff0c;选择Git Bash Here。 git remote -v //查看自己的文件有几个关联的仓库git init //初始化文件夹为git可远程建立链接的文件夹…

c语言基础程序——经典100道实例。

c语言基础程序——经典100道实例 001&#xff0c; 组无重复数字的数002&#xff0c;企业发放的奖金根据利润提成003&#xff0c;完全平方数004&#xff0c;判断当天是这一年的第几天005&#xff0c;三个数由小到大输出006&#xff0c;输出字母C图案007&#xff0c;特殊图案008&…

前端一键复制解决方案分享

需求背景 用户需要对流水号进行复制使用&#xff0c;前端的展示是通过样式控制&#xff0c;超出省略号表示&#xff0c;鼠标悬浮展示完整流水号。此处的鼠标悬浮展示采用的是:title&#xff0c;这样就无法对文本进行选中。 下面是给出一键复制的不同的解决方案&#xff0c;希望…