👉 这是一个或许对你有用的社群
🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入「芋道快速开发平台」知识星球。下面是星球提供的部分资料:
《项目实战(视频)》:从书中学,往事中“练”
《互联网高频面试题》:面朝简历学习,春暖花开
《架构 x 系统设计》:摧枯拉朽,掌控面试高频场景题
《精进 Java 学习指南》:系统学习,互联网主流技术栈
《必读 Java 源码专栏》:知其然,知其所以然
👉这是一个或许对你有用的开源项目
国产 Star 破 10w+ 的开源项目,前端包括管理后台 + 微信小程序,后端支持单体和微服务架构。
功能涵盖 RBAC 权限、SaaS 多租户、数据权限、商城、支付、工作流、大屏报表、微信公众号等等功能:
Boot 地址:https://gitee.com/zhijiantianya/ruoyi-vue-pro
Cloud 地址:https://gitee.com/zhijiantianya/yudao-cloud
视频教程:https://doc.iocoder.cn
来源:juejin.cn/post/
7213636024112234551
工作当中经常会遇到既需要开启事务管理,同时也需要同步保证线程安全的场景。
比如一个方法
@Transactional
public synchronized void test(){
//
}
不知道大家有没有这样写过?
这样写会有问题吗?
众所周知,spring使用动态代理加AOP实现事务管理。那么上面的方法实际上需要简化成3个步骤:
void begin();
@Transactional
public synchronized void test(){
//
}
void commit();
// void rollback();
先看事务本身,这里简化为test()
一个方法,从而忽略事务的传播来看,它是不受synchronized
影响的。因为它能正常commit或rollback不受其它线程影响。
再看同步这一块,这里很明显就有点问题了。synchronized
至少无法作用于事务开启提交这一步骤。假设一个线程先在此方法做了update,在return之后commit之前,另一个线程进入了此方法,进行了select,除非事务隔离级别为读未提交(READ_UNCOMMITTED
)(话说回来谁会在生产环境用这种隔离级别呢),不然第二个线程读到的是未修改的值。
而这肯定并未与使用synchronized 的初衷相符。
口说无凭,show you me code。
@Transactional
public synchronized void test(){
log.info("表哥,我进来了哦!");
// select
Person person = personDao.selectByPrimaryKey("1");
log.info("person.name = " + person.getName());
// update
person.setName("你是谁?你根本不是我表哥!");
int result = personDao.updateByPrimaryKeySelective(person);
log.info("result = " + result);
}
然后在TransactionAspectSupport.commitTransactionAfterReturning()
加个断点。
![c02deac38cca2732e1eb1616c1136e5c.jpeg](https://i-blog.csdnimg.cn/blog_migrate/273947ba9ea8c30688f6a8619b653617.jpeg)
再模拟两个请求执行,在断点处观察第2次请求与第1次请求select到的值是不是一致。一致说明synchronized
在这里没有达到预期目的。
注意断点时选thread不要选all,不然第2个线程进不来。
![d0d05d3c40287ee91d78eb4d626a6639.jpeg](https://i-blog.csdnimg.cn/blog_migrate/36616d22584eea43a9283edbdfa0174a.jpeg)
通过执行结果,可以看到第2个线程执行的时候访问到了还是原值张三,并在update时等待锁超时。
![ca9ceafc5dafe34902c6ddf54ae326e2.jpeg](https://i-blog.csdnimg.cn/blog_migrate/72bfcb3c972d59d71e836e5d99549fdb.jpeg)
通过什么方法可以让 synchronized 达到预期效果呢?
手动开启事务?pass。
事务传播级别?无关。
事务隔离级别?
读未提交 不实用。还是试试效果。
加上隔离级别
![0ec30aaa110fe43f18f952b56f8abec3.jpeg](https://i-blog.csdnimg.cn/blog_migrate/5dc43a9de88d33648c015f69e6410b8d.jpeg)
看看效果
![d5b7e8dc70a75c19c9512f13fb2c419f.jpeg](https://i-blog.csdnimg.cn/blog_migrate/7bf780b8bda0299f177a3d2a5d88f7d7.jpeg)
能满足要求,但是谁会在生产环境使用读未提交级别?
依次往上,读已提交,经测试不满足,可重复读,mysql默认级别,一开始就是,不满足。
SERIALIZABLE
这倒是走向了另一个极端,通过测试可以看到,由于在SERIALIZABLE
隔离级别下,会给表加个锁,因此在第2个线程执行到Select的时候就会一直等待到锁超时。
在这一个固定的测试场景曲线满足了业务要求,但是它还是进入了test方法,因此不满足synchronized
的要求。
![4092a60315c6bfe4b2acd46cc1d0a3d5.jpeg](https://i-blog.csdnimg.cn/blog_migrate/e4bd4a7963bbf4130e9a8934154c91bc.jpeg)
而且这种隔离级别和读未提交一样属于两个极端,它会极大的抑制并发数,在生产环境中也极少使用,在这里属于既不实用也不好用。
for update
给select查询语句手动上锁。
![b1592c4173001a35ce53d9ca78627907.jpeg](https://i-blog.csdnimg.cn/blog_migrate/303d8285e92159947871ba58f67e5f53.jpeg)
测试结果就不截图了,这种是比SERIALIZABLE
要实用一些,它只加行锁,其它的话类似,并不能完全达到synchronized
的要求。
是否使用看场景。
给test方法加一层调用方法
把transcational
和synchronized
分开,作用在两个方法。比如synchronized
在上层方法。
![e59a7acbcecb9008852a162c8174d45f.jpeg](https://i-blog.csdnimg.cn/blog_migrate/e979c76dc1d33055967bfe62f73900ba.jpeg)
千万别写成这样,这样事务不生效了。
最好也别写到最顶层如controller层,这样感觉把通道门口就给堵死了的感觉。只是加个中间层。
![799ca80315d849af96554efc8e4d5d86.jpeg](https://i-blog.csdnimg.cn/blog_migrate/41d18727cb43c3b67be13e86a50090e6.jpeg)
这样,表哥有了第1次的经验过后,表妹在第2次来的时候就被小区安保直接给拦住啦,在表哥完全跑路之前没有撬锁的机会了。
![5dcf5faf1e14d2b5ad944f2f52c687c4.jpeg](https://i-blog.csdnimg.cn/blog_migrate/2a856e6fe1d07408f0e6b10147a57d15.jpeg)
另外,一定是synchronized
在调用层,transcational
在被调用层。不能弄反了,弄反了就和之前没区别了。
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/YunaiV/ruoyi-vue-pro
视频教程:https://doc.iocoder.cn/video/
总结
当 transcational
遇上 synchronized
,不要搞在一起,会出事。如果要用最好是分开。
欢迎加入我的知识星球,全面提升技术能力。
👉 加入方式,“长按”或“扫描”下方二维码噢:
星球的内容包括:项目实战、面试招聘、源码解析、学习路线。
文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)