佛山营销网站建设服务,在哪个网站上做外贸好,网站构建免费,内蒙古城乡建设和住房建设厅网站事务与锁 - Transactional与Synchronize#x1f970;前言问题回放问题一1、代码与结果复现2、原因分析3、解决方法问题二1、问题复现2、原因分析事务Transactional与锁synchronized1、synchronized与Transactional区别2、可能带来的问题3、针对问题二的解决前言
最近工作中遇…
事务与锁 - Transactional与Synchronize前言问题回放问题一1、代码与结果复现2、原因分析3、解决方法问题二1、问题复现2、原因分析事务Transactional与锁synchronized1、synchronized与Transactional区别2、可能带来的问题3、针对问题二的解决前言
最近工作中遇到某些七七八八的问题就是与事务和锁、并发都有着紧密联系相关的问题所在。主要情况是通过调用方法获取编号而这个编号是递增有序的并且存在于数据库中简单理解就是需要用到这种编号(以下称任务编号)需要从数据库获取出来在1最为本次需要的编号然后在存回数据库中提供下次使用。直观来看是没得问题的但是可能在某次并发的时候出现编号相同着属实很令人头疼在经过领导的指导下是完美的解决了接下来复盘一下。
问题回放
因为公司项目使用WQL作为持久层但是我这次使用Mybatis-Plus效果大差不差。新建springboot项目基础创建不在赘述。主要创建一张简单的数据表就一个id和number字段。之后用mp自动生成代码。
问题一
最外层加上Transactional注解并且在对编码操作的方法也加了Transactional注解为了防止并发问题加了锁。此时模拟的问题是在嵌套事务中父事务延时提交导致获得到的数据出错。
1、代码与结果复现
直接在控制层中调用这里会加上Transactional并且一开始数据库数据为{“id”:“1”, “number”:“460”}
GetMapping(/t1)
Transactional(rollbackFor Exception.class)
public void getTest1() {String n countNumService.getCount();System.out.println( t1 : n);try {Thread.sleep(6000);} 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);// 忽略其他的增删操作
}对数据库获取操作的方法加上Transactional与synchronized。
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();
}通过IDEA的插件RestServices(也可以用postman)测试先请求/t1接口这里会睡眠6s来模拟事务延时提交在去请求/t2接口可以看出得到的数据会是相同的。
2、原因分析
只有两个线程都能形成两个相同的code仔细分析一下假设synchronized锁是锁住的那为什么会出现这样的问题呢当t1线程访问getCount()方法此时他拿到synchronized的锁在进行获取数据并且1操作后进行update操作此时synchronized的锁已经被释放了但是父事务却没有提交也就是没有写回到数据库中接下来t2线程过来了也拿到了这把锁又从数据库获取了数据此时获得的是脏数据最后就会导致出现了相同的code。
3、解决方法
这里是因为事务的先后提交导致可以使用Transactional注解的配置来解决使用Transactional(propagation Propagation.REQUIRES_NEW)每次都会启动一个新的事务是Spring事务传播机制的一种级别表示当前方法必须在自己的事务中运行如果当前已经存在一个事务则会挂起该事务创建一个新的事务用于执行当前方法。当当前方法执行完成后新事务被提交原事务恢复执行。
问题二
此次测试是使用apache-jmeter-5.4.3测试工具来测试20次请求的并发情况接口每次会创建100次一共会又20*100次在以上代码的条件下编写新的接口。
1、问题复现
以下是每次请求都会创建100个线程getCount()除了使用propagation Propagation.REQUIRES_NEW其他不变。
GetMapping(/t3)
Transactional(rollbackFor Exception.class)
public void getTest1() {for (int i 0; i 100 ; i) {new Thread(()-{String taskCode countNumService.getCount();System.out.println(t1 Thread.currentThread().getName() code: taskCode);}).start();}
}测试结果就会发现并发下出现了种种问题会出现相同的code。
2、原因分析
这是由于父子事务嵌套子事务与锁一起使用导致了synchronized锁的失效。当我把事务去掉的时候则就不会出现并发的问题。 问题就是出在事务与synchronized锁的共同使用导致的如果既要保证并发不出问题又要保证在异常的时候需要回滚数据在实际应用中getCount()内部又其他对数据库的操作因此需要事务来保证异常的回滚也就是一定需要父子事务的嵌套在这种情况下需要怎么做处理 首先先来了解事务Transactional与锁synchronized一同使用会带来什么问题。
事务Transactional与锁synchronized
事务Transactional与锁synchronized他们是两种不一样的机制两种机制要是一起使用可能会出现一些问题。
1、synchronized与Transactional区别
首先synchronized锁是用来实现线程同步防止多个线程同时访问共享资源导致的并发问题。而Transactional是Spring框架中用于管理事务的机制用于保证多个操作的四个特性原子性、一致性、隔离性和持久性。
2、可能带来的问题
事务可能被锁定如果在一个方法中使用synchronized锁定了某个共享资源同时该方法又使用了Transactional来管理事务那么其他线程在访问该方法时可能会被阻塞因为事务被锁定了。死锁问题如果在多个线程中同时使用synchronized和Transactional可能会导致死锁问题因为synchronized锁定的资源可能被多个线程同时访问而Transactional又会对这些操作进行管理可能会导致事务的死锁。性能问题如果在一个高并发的系统中同时使用synchronized和Transactional可能会导致性能问题因为synchronized会导致线程阻塞而Transactional又会增加事务的开销从而影响系统的性能。
3、针对问题二的解决
可以将子事务中的锁移到父事务中优化一下细粒度就只对获取任务编码的这条语句进行上锁。
GetMapping(/t4)
Transactional(rollbackFor Exception.class)
public void getTest4() {for (int i 0; i 100 ; i) {new Thread(()-{synchronized(CountNumController.class) {String taskCode countNumService.getCount();System.out.println(t4 Thread.currentThread().getName() code: taskCode);}}).start();}
}这样就能保证并发不出问题也能保证将父子事务都存在。
创作不易如有错误请指正感谢观看记得点赞哦