我的编程空间,编程开发者的网络收藏夹
学习永远不晚

当Transactional遇上synchronized的解决方法分享

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

当Transactional遇上synchronized的解决方法分享

问题情形

假设代码如下:

//controller层:
@GetMapping("/t1")
@Transactional(rollbackFor = Exception.class)
public void getTest1() {
    String n = countNumService.getCount();
    System.out.println(" t1 : " + n);
    try {
        Thread.sleep(60000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
}

@GetMapping("/t2")
@Transactional(rollbackFor = Exception.class)
public void getTest2() {
    String n = countNumService.getCount();
    System.out.println(" t2 : " + n);
    // 忽略其他的增删操作
}

//service层:
@Override
@Transactional(rollbackFor = Exception.class)
public synchronized String getCount() {
    // 获取
    CountNum countNum = countNumMapper.selectById(1);

    countNum.setNumber(countNum.getNumber() + 1);
    // 修改
    countNumMapper.updateById(countNum);

    return countNum.getNumber().toString();
}

问题分析

首先,在 getTest1()getTest2() 这两个方法中都加了 @Transactional 注解,因此它们会分别开启自己的事务。假设在某一刻,两个线程同时调用了 /t1/t2 接口,并且 /t1 接口中执行了较长的睡眠操作,于是 /t2 的业务逻辑率先完成,并且更新了数据库中的Number字段。

随后 /t1 的业务逻辑也完成了,但是由于之前被阻塞了 60 秒钟,此时读取到的计数器值已经过期了,不能反映最新的状态。因此 /t1 返回的结果将是过期的数据,与 /t2 返回的结果不一致。所以这段代码存在一个并发问题,可能导致数据的不一致性

解决方法

这里我给出常用的解决方法:

  • getCount() 方法上的 synchronized 去掉,使用乐观锁的方式来控制并发访问。
  • /t1/t2 两个接口的事务设置为同一个事务,即两个接口共享同一个事务上下文。可以通过 Spring 的声明式事务管理机制来实现。(在 Spring 框架中,我们可以使用 @Transactional 注解来声明式地管理事务。使用 PROPAGATION_REQUIRED 属性可以表示当前方法需要加入到一个存在的事务中,如果不存在,则开启新的事务。)
  • getCount() 方法中,进行增量更新,而不是直接把Number字段加 1,例如使用 update count_num set number = number + 1 where id = ? 等 SQL 语句来实现。
  • 保留synchronized关键字的情况下,添加事务管理器进行手动事务管理。

代码参考

1.乐观锁方案

@Override
@Transactional(rollbackFor = Exception.class)
public String getCount() {
    CountNum countNum = countNumMapper.selectById(1);
    // 使用版本号作为乐观锁
    int version = countNum.getVersion();
    countNum.setNumber(countNum.getNumber() + 1);
    countNum.setVersion(version + 1);
    // 更新操作必须要包含版本号字段
    int rows = countNumMapper.updateById(countNum);
    if (rows == 0) {
        throw new OptimisticLockException("事务中更新失败");
    }
    return countNum.getNumber().toString();
}

2.将 /t1 和 /t2 两个接口的事务设置为同一个事务。在 CountNumService 类的事务注解上添加了 propagation = Propagation.REQUIRED 属性,表示当前方法需要加入到一个已存在的事务中。此时,如果 /t1/t2 调用的是同一个 CountNumService 实例,则它们将共享同一个事务上下文。

@Service
public class CountNumService {
    @Autowired
    private CountNumMapper countNumMapper;
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public String getCount() {
         // 与之前代码相同
    }
}
@RestController
public class CountNumController {
    @Autowired
    private CountNumService countNumService;
    @GetMapping("/t1")
    public void getTest1() {
        String n = countNumService.getCount();
        System.out.println(" t1 : " + n);
        try {
            Thread.sleep(60000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
    @GetMapping("/t2")
    public void getTest2() {
        String n = countNumService.getCount();
        System.out.println(" t2 : " + n);
        // 忽略其他的增删操作
    }
}

3.SQL中增量更新方案

@Mapper
public interface CountNumMapper {
    @Update("UPDATE count_num SET number = number + 1 WHERE id = 1")
    int updateNumber();
}
@Override
@Transactional(rollbackFor = Exception.class)
public String getCount() {
    // 直接执行 SQL 语句进行增量更新操作
    countNumMapper.updateNumber();
    // 再查询一次获取最新值
    CountNum countNum = countNumMapper.selectById(1);
    return countNum.getNumber().toString();
}

4.手动事务管理方案

@Service
public class CountNumService {
    @Autowired
    private CountNumMapper countNumMapper;

    // 使用spring事务管理器
    @Autowired
    private PlatformTransactionManager transactionManager;

    public synchronized String getCount() {
        TransactionStatus status = null;
        try {
            DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
            //将传播行为设置为 PROPAGATION_REQUIRED,以确保当前方法在事务内执行:
            definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
            //获取当前事务的状态信息:
            status = transactionManager.getTransaction(definition);
            
            CountNum countNum = countNumMapper.selectById(1);
            countNum.setNumber(countNum.getNumber() + 1);

            int rows = countNumMapper.updateById(countNum);
            if (rows == 0) {
                throw new RuntimeException("事务中更新失败");
            }
            //提交事务:
            transactionManager.commit(status);
            return countNum.getNumber().toString();
        } catch (Exception e) {
            if (status != null) {
                //回滚事务:
                transactionManager.rollback(status);
            }
            throw e;
        }
    }
}

到此这篇关于当Transactional遇上synchronized的解决方法分享的文章就介绍到这了,更多相关Transactional synchronized内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

当Transactional遇上synchronized的解决方法分享

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

当Transactional遇上synchronized的解决方法分享

前些时间刚好刷到了有关于“#【事务与锁】当Transactional遇上synchronized”这一类的文章,感觉这也是工作中经常会遇到的一类问题了。所以就针对这个话题进行了分析并整理了常用的解决方法,希望对大家有所帮助
2023-05-19

当win8.1遇上错误651的解决方法

不少用户遇到这样一个问题,自从升级了win8.1系统之后,每次开机都需要连接两次才能正常上网,有时候还会遇上错误651,上个网实在伤不起,怎样才能解决这个错误651问题呢,让我们来看下下面的解决步骤吧。第一步、按windows+R键,在运行
2022-06-04

遇到Discuz验证失败怎么办?快速解决方法分享

遇到Discuz验证失败怎么办?快速解决方法分享,需要具体代码示例在进行网站开发或者论坛建设过程中,有时会遇到Discuz验证失败的情况,这给我们的工作带来了一定的困扰。不过,只要采取正确的解决方法,这个问题其实并不难解决。本文将详细介绍
遇到Discuz验证失败怎么办?快速解决方法分享
2024-03-10

解决PyCharm无法打开的方法分享

标题:如何解决PyCharm无法打开的问题PyCharm是一款功能强大的Python集成开发环境,但有时候我们可能会遇到无法打开PyCharm的问题。在本文中,我们将分享一些常见的解决方法,并提供具体的代码示例。希望能帮助到遇到这个问题的
解决PyCharm无法打开的方法分享
2024-02-22

解决Oracle错误3114的有效方法分享

解决Oracle错误3114的有效方法分享,需要具体代码示例Oracle数据库是常用的企业级关系型数据库管理系统,但在使用过程中经常会遇到各种错误。其中,错误3114是一个比较常见的错误,在解决过程中需要仔细分析并找出根本原因。本文将分享
解决Oracle错误3114的有效方法分享
2024-03-09

百度分享代码无法水平居中的解决方法

这篇文章将为大家详细讲解有关百度分享代码无法水平居中的解决方法,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。百度在搜索结果中偏爱自己的产品是无用质疑的,所以,越来越多的站长决定给网站页面加上
2023-06-12

Session对象失效的客户端解决方法284435分享

这篇文章主要介绍了Session对象失效的客户端解决方法,需要的朋友可以参考下
2023-05-20

局域网共享提示登入失败禁止当前的账户的解决方法

在局域网共享的时候,总是会碰到各种问题,比如在办公是有WIN8,WIN7和XP系统的情况下,不同系统之间的局域网共享出现的问题更多,比如在访问其他编程电脑的时候出现“登入失败,禁止当前的账户”的问题,很多人都不知道这
2023-06-04

Python下载文件后路径丢失的解决方法分享

使用pathlib模块解决python下载文件后路径丢失问题:创建pathlib的path对象,指定要下载文件的路径。使用requests库下载文件并保存到指定路径。使用path.resolve()方法获取文件的绝对路径。使用绝对路径访问或
Python下载文件后路径丢失的解决方法分享
2024-04-04

android开发环境遇到adt无法启动的问题分析及解决方法

开始研究android开发,搭建开发环境的时候就出了问题……果然是好事多磨~ 安装了jdk,配置环境变量,安装了完整版的adt、创建了helloworld程序,启动的时候就报错 “Please ensure that adb is corr
2022-06-06

PHP结合vue导出excel出现乱码的解决方法分享

这篇文章主要为大家详细介绍了PHP结合vue导出excel出现乱码的解决方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
2023-02-03

Android Activity切换(跳转)时出现黑屏的解决方法 分享

在两个Activity跳转时,由于第二个Activity在启动时加载了较多数据,就会在启动之前出现一个短暂的黑屏时间,解决这个问题比较简单的处理方法是将第二个Activity的主题设置成透明的,这样在启动第二个Activity时的黑屏就
2022-06-06

Win7开机黑屏error15:file not found的两种解决方法分享

Win7开机黑屏error15:file not found错误通常是由于操作系统的启动文件丢失或损坏导致的。以下是两种解决方法:方法一:修复启动文件1. 使用Win7安装光盘或USB启动盘启动计算机。2. 在安装界面中选择“修复计算机”选
2023-08-31

windows中局域网共享提示登入失败禁止当前的账户的解决方法

windows中局域网共享提示登入失败禁止当前的账户的解决方法,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。在局域网共享的时候,总是会碰到各种问题,比如在办公是
2023-06-13

解决MongoDB技术开发中遇到的数据分析问题的方法研究

解决MongoDB技术开发中遇到的数据分析问题的方法研究,需要具体代码示例摘要:随着大数据的快速发展,数据分析变得越来越重要。MongDB作为一种非关系型数据库,具有高性能和可扩展性的优势,因此在数据分析领域也逐渐受到广泛关注。本文将重点研
2023-10-22

xp系统用户无法访问win10电脑上共享资源的解决方法

虽然微软已经发布了win10正式版,但是很多用户因为某些原因仍然无法放弃xp系统。不过,最近有一位XP系统用户反馈想通过局域网访问Windows10电脑上的共享资源,可是遇到了无法正常访问的问题,这是怎么回事呢?下面,就随小编看看该问题的具
2023-05-21

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录