在synchronized 锁住方法的情况下,竟然出现了脏写

Tips
昨天本来打算是准备着一支烟 一杯咖啡 一个bug写一天的,突然我们组长跟我们说线上环境报错了,
还出现了"服务器异常,请联系管理员"
这特么不是一级事故吗?虽然有测试再前面扛枪。但是是我负责的直播模块,心理慌的一批(ps 报错图当时没保存了)

分析事故原因

因为是报错(因为我做这条数据查询的时候是selectOne 所以会报出现了sql异常) 原因到是很快找到了 数据库出现了脏写如图:

我负责的是直播模块 其中的一个业务是直播结束后第三方会通知我去拉取直播的回放,
但是这个回放有可能一条,也有可能是多条,但是我们的业务要求是只需要保存一条直播回放所以我这会做如下操作:

我再做插入之前我会做一个校验,并且我还加了一个方法级别的锁 并且线上我们只有一个副本,
竟然还出现了脏写 我的fuck,我这是见了鬼了吧

解决问题的过程

我怀着百私不得其解的心理打算去找答案

首先我模拟了一个并发环境:

@Testpublic void TEST_TX() throws Exception {int N = 2;CountDownLatch latch = new CountDownLatch(N);for (int i = 0; i < N; i++) {Thread.sleep(100L);new Thread(() -> {try {latch.await();System.out.println("---> start " + Thread.currentThread().getName());Thread.sleep(1000L);CourseChapterLiveRecord courseChapterLiveRecord = new CourseChapterLiveRecord();courseChapterLiveRecord.setCourseChapterId(9785454l);courseChapterLiveRecord.setCreateTime(new Date());courseChapterLiveRecord.setRecordEndTime(new Date());courseChapterLiveRecord.setDuration("aaa");courseChapterLiveRecord.setSiteDomain("ada");courseChapterLiveRecord.setRecordId("aaaaaaaaa");courseChapterLiveRecordServiceImpl.saveCourseChapterLiveRecord(courseChapterLiveRecord);System.out.println("---> end " + Thread.currentThread().getName());} catch (Exception e) {e.printStackTrace();}}).start();latch.countDown();}}

通过CountDownLatch 去模拟并发看看数据是否会有问题:结果测试线的数据如下:

我去还真出现了 而且是一部分出现脏写,一部分没有成功,我特么 fuck 心理一万次想说这特么我怎么找
测了十来次 然后觉得肯定是有问题的 然后冷静下来 因为我打了日志 发现2个线程确实是顺序执行的(这里的截图就没有贴了)
众所周知,synchronized方法能够保证所修饰的代码块、方法保证有序性、原子性、可见性。 那么这说明什么呢 我一想肯定Synchronized 它是起到它的作用的 一个线程执行完成之后,另外一个线程再来执行, 突然灵光一闪 是不是下一个线程再做幂等校验的时候 读到了上一次还没有提交的事务 所以造成了脏读脏写的原因呢 然后我把再类上的 @Transactional 注解去掉

果然后面测了几次 再也没出现上面的情况了

Tips 特别感谢一位不愿透露姓名的大佬的指出说我没有把标题的内容说清楚和后面的解决问题的收场的时候有点草率

在这里 我再好好的说一下我标题是 在Spring事务管理下,Synchronized为啥还线程不安全? 其实有是自己并没有用Synchronized 锁住 Spring 的事务
因为我的列子上的@Transaction注解是再类上面(也就是再方法上面)Spring的声明事事务他是利用了aop的思想
我虽然锁住了第一个线程 但是等到第一个线程的事务 还没提交的时候,第二个线程就去查询了 所以就会导致线程不安全问题

解决问题

方案1 很简单 那就是不开事务就行了,再这个方法上不加事务就行 因为 Synchronized 可以保证线程安全。 这个方案的意思就是说不要再同一个方法上用@Transaction 和 Synchronized 例子图就没有贴了 就像我前面的 把注解去掉就好了 (但是前提你这个方案确定是不需要事务)

方案2 再这个里面再调用一层service 让那个方法提交事务,这样的话加上Synchronized 也能保证线程安全。 方案2我贴下代码吧

 @Overridepublic synchronized void saveCourseChapterLiveRecord(CourseChapterLiveRecord courseChapterLiveRecord) {saveRecord(courseChapterLiveRecord);}@Transactionalpublic void saveRecord(CourseChapterLiveRecord courseChapterLiveRecord) {//先查数据看是否已经存了if (findOrder(courseChapterLiveRecord)){ return;}int row = this.insertSelective(courseChapterLiveRecord);if (row<1){log.info("把录播的信息插入数据库失败 参数是->{}", JSON.toJSONString(courseChapterLiveRecord));throw new RRException("把录播的信息插入数据库失败");} }

其实也就是说把事务包裹在Synchronized 里面

先自我批评一下
在技术的道路上真的不要自己觉得是什么就是什么 上面的代码是错误的 其实我并没有测试过 就贴到文章上了 这是一个大忌 为什么很多技术文章有问题 因为很多就像我上面的一样 所以敦促自己以后做事情还是要扎扎实实

感谢 紫雨飞星 读者提出我的错误 具体错误的原因是因为调用savRecord方法的时候使用的是this对象,其实是没有被AOP处理的,也就是这个Transactional不会生效~~~

修改后的代码 自己注入自己

 @Overridepublic synchronized void saveCourseChapterLiveRecord(CourseChapterLiveRecord courseChapterLiveRecord) {courseChapterLiveRecordServiceImpl.saveRecord(courseChapterLiveRecord);}@Transactionalpublic void saveRecord(CourseChapterLiveRecord courseChapterLiveRecord) {//先查数据看是否已经存了if (findOrder(courseChapterLiveRecord)){ return;}int row = this.insertSelective(courseChapterLiveRecord);if (row<1){log.info("把录播的信息插入数据库失败 参数是->{}", JSON.toJSONString(courseChapterLiveRecord));throw new RRException("把录播的信息插入数据库失败");} }

利用中午的时间测了几次 确实是不会出现线程安全问题了

方案3 用redis 分布式锁 也是可以的 就算是多个副本也是能保证线程安全。这个后面的文章会有写到

结论

在多线程环境下,就可能会出现:方法执行完了(synchronized代码块执行完了),事务还没提交,别的线程可以进入被synchronized修饰的方法,再读取的时候,读到的是还没提交事务的数据,这个数据不是最新的,所以就出现了这个问题。

Synchronized 失效关键原因:是因为Synchronized锁定的是当前调用方法对象,而Spring AOP 处理事务会进行生成一个代理对象,并在代理对象执行方法前的事务开启,方法执行完的事务提交,所以说,事务的开启和提交并不是在 Synchronized 锁定的范围内。出现同步锁失效的原因是:当A(线程) 执行完insertSelective()方法,会进行释放同步锁,去做提交事务,但在A(线程)还没有提交完事务之前,B(线程)进行执行findOrder() 方法,执行完毕之后和A(线程)一起提交事务, 这时候就会出现线程安全问题。

在Spring事务管理下,Synchronized为啥还线程不安全?相关推荐

  1. 【Spring 】Synchronized锁在Spring事务管理下,为啥还线程不安全?

    1.概述 转载:Synchronized锁在Spring事务管理下,为啥还线程不安全? 知乎问题:知乎

  2. 同一事务多次加for_Synchronized锁在Spring事务管理下,为啥还线程不安全?

    前言 只有光头才能变强. 文本已收录至我的GitHub仓库,欢迎Star:https://github.com/ZhongFuCheng3y/3y 大年初二,朋友问了我一个技术的问题(朋友实在是好学, ...

  3. Spring事务管理 与 SpringAOP

    1,Spring事务的核心接口 Spring事务管理的实现有许多细节,如果对整个接口框架有个大体了解会非常有利于我们理解事务,下面通过讲解Spring的事务接口来了解Spring实现事务的具体策略.  ...

  4. 什么是事务的传播_这么漂亮的Spring事务管理详解,你不来看看?

    事务概念回顾 什么是事务? 事务是逻辑上的一组操作,要么都执行,要么都不执行. 事物的特性(ACID): 原子性: 事务是最小的执行单位,不允许分割.事务的原子性确保动作要么全部完成,要么完全不起作用 ...

  5. Spring 事务管理高级应用难点剖析

    Spring 事务管理高级应用难点剖析: 第 1 部分 http://www.ibm.com/developerworks/cn/java/j-lo-spring-ts1/index.html htt ...

  6. Spring 事务管理高级应用难点剖析--转

    第 1 部分 http://www.ibm.com/search/csass/search/?q=%E4%BA%8B%E5%8A%A1&sn=dw&lang=zh&cc=CN& ...

  7. Spring事务管理(详解+实例)

    写这篇博客之前我首先读了<Spring in action>,之后在网上看了一些关于Spring事务管理的文章,感觉都没有讲全,这里就将书上的和网上关于事务的知识总结一下,参考的文章如下: ...

  8. 多数据源 事务管理_可能是最漂亮的Spring事务管理详解

    事务概念回顾 什么是事务? 事务是逻辑上的一组操作,要么都执行,要么都不执行. 事物的特性(ACID): 原子性: 事务是最小的执行单位,不允许分割.事务的原子性确保动作要么全部完成,要么完全不起作用 ...

  9. springaop事务逻辑原理_架构师:一篇文章掌握——Spring 事务管理

    对大多数Java开发者来说,Spring事务管理是Spring应用中最常用的功能,使用也比较简单.本文主要逐步介绍Spring事务管理的相关知识点及原理,作为Spring事务管理的学习总结. 一.关键 ...

最新文章

  1. 微软浏览器适配问题前端_「图」微软新贡献:修复Chromium浏览器的奇怪触控板手势问题...
  2. android 隐藏闹钟通知,redis环境搭建
  3. rap技术原理_「水深坑多」做分子海绵,你还需要了解这些技术
  4. linux串口中断_5年匠心之作,深度探索Linux虚拟化
  5. Codeforces 1070A Find a Number(BFS) 2018-2019 ICPC, NEERC, Southern Subregional Contest Problem A
  6. CentOS 下使用yum安装nodejs
  7. LeetCode 504. Base 7
  8. Rest上传文件(利用jersey)
  9. 纯新手DSP编程--5.30--任务的通信和同步
  10. C中的C文件与h文件辨析(转)
  11. 路由器交换机[置顶] 路由器和交换机的综合实验⑵
  12. 你以为服务器关了这事就结束了? - XcodeGhost截胡攻击和服务端的复现,以及UnityGhost预警...
  13. Gallery3D笔记
  14. 【代码审计】代码安全测试的方法
  15. 编译libpng + zlib
  16. 2021年第4季度记账理财应用监测,头部集聚加强,领跑者转型发展
  17. 2022-JavaScript-过滤数组中的undefined,null,空串,NaN
  18. ADC0809芯片简介
  19. 微信公众号开发以及测试公众号菜单配置
  20. vue3.0组件之父传子,子传父,父传孙

热门文章

  1. 浅谈代码的执行效率(1):算法是关键
  2. 跟我一起写 Makefile(七)
  3. 《大话数据结构》第3章 线性表 3.8.2 单链表的删除
  4. Windows未能启动,原因可能是最近更改了硬件或软件,解决此问题的步骤
  5. SQL 基础正则表达式(二十三)
  6. ADO.NET开发总结(学习笔记)
  7. 星座图与IQ调制总结+BPSK、QPSK、8PSK、16QAM等的区别与总结
  8. FLV提取AAC音频单独播放并实现可视化的频谱
  9. 使用web worker和webassembly技术
  10. ubuntu系统home(主文件夹)中的内容在桌面的显示