java陷阱之spring事物管理导致锁无效
模拟锁情况无效
1.创建一个表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `demo`;
CREATE TABLE `demo` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`product_name` varchar(20) DEFAULT NULL,
`stock_number` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `index_name` (`product_name`)
) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8;
BEGIN;
INSERT INTO `demo` VALUES (1, '肥皂', 1000);
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;

2.创建一个下单扣除的方法防止并发导致超买超卖以及脏读加锁
ps 我这里用的redis实现的分布式锁可以直接替换成synchronized测试
//事物方法 保证一致性
@Transactional
public boolean deductNumber(Long id,int i){
//定义锁 库存id为id的数据
RLock[] locks = new RLock[]{redissonClient.getLock(String.valueOf(id))};
RedissonMultiLock redissonMultiLock = null;
redissonMultiLock = new RedissonMultiLock(locks);
boolean getLock = false;
try {
if (redissonMultiLock != null) {
//尝试获得锁
getLock = redissonMultiLock.tryLock();
if (!getLock) {
return false;//系统繁忙请重试
}
}
RowMapper<Demo> rowMapper = new BeanPropertyRowMapper<Demo>(Demo.class);
//获得指定产品的库存
Demo demo= jdbcTemplate.queryForObject("select * from demo where id=?",rowMapper,id);
//判断库存是否充足
if(demo.getStockNumber()<i){
return false;//库存不足 剩余库存demo.getStockNumber()
}
//库存扣除
demo.setStockNumber(demo.getStockNumber()-i);
//持久化到数据
jdbcTemplate.update("update demo set stock_number=? where id=?",demo.getStockNumber(),demo.getId());
} catch (Exception e) {
return false;
} finally {
//释放锁
if (redissonMultiLock != null && getLock) {
redissonMultiLock.unlock();
}
}
return true;
}
这里分为五步 1获得锁 2查询数据判断库存是否充足 3.库存扣除 4.持久化到数据库 5.释放锁
3.测试并发场景
/**
* 模拟50个人下单 同时扣除库存
*/
@Test
public void run() {
int threand = 50;//定义50个线程
ExecutorService executorService = Executors.newFixedThreadPool(threand);
List<Future<Integer>> futures = new ArrayList<Future<Integer>>();
for (int i = 0; i < threand; i++) {
futures.add(executorService.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int succeedCount = 0;
//重复扣除1000次
for (int j = 0; j < 1000; j++) {
boolean isSuccess = tbDmsBasisCompanyConfigureService.deductNumber(1L, 1);
//如果扣除成功+1
if (isSuccess) {
succeedCount++;
}
}
return succeedCount;
}
})); }
int count = 0;
for (int i = 0; i < futures.size(); i++) {
try {
count += futures.get(i).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
//打印成功的数量
System.out.println(count); }
4.验证结果

可以发现超卖了 我们库存1000 但是现在卖出1179 再看我们的库存

189 数据也是异常的
导致异常的分析
由于我们的事物开启和关闭是由spring托管的 spring事物管理是根据代理模式实现的 我可以把spring的代理方法简单看成以下
ps:大致这样 有空看完源码再回来补充
public boolean invoke(){
//开启事物
......
boolean result= tbDmsBasisCompanyConfigureService.deductNumber(1L, 1);
//根据事物状态提交和回滚事物
......
return result
}
用户1 1获得锁 2查询数据判断库存是否充足 3.库存扣除 4.持久化到数据库 5.释放锁 库存还剩999 (并发情况spring还没来得及提交事物)
用户2 因为用户1释放了锁 所以用户2成功获得锁 因为用户1事物还没来得及提交 RR(mysql默认)或者RC隔离级别 别的事物是不能读取到未提交的数据 所以用户2查询库存还是1000 这里脏读 后面导致超买超卖以及库存扣除
解决方式1
在外部加锁
/**
* 模拟50个人下单 同时扣除库存
*/
@Test
public void run() {
int threand = 50;//定义50个线程
ExecutorService executorService = Executors.newFixedThreadPool(threand);
List<Future<Integer>> futures = new ArrayList<Future<Integer>>();
for (int i = 0; i < threand; i++) {
futures.add(executorService.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int succeedCount = 0;
//重复扣除1000次
for (int j = 0; j < 1000; j++) {
//定义锁 库存id为1的数据
RLock[] locks = new RLock[]{redissonClient.getLock("1")};
RedissonMultiLock redissonMultiLock = null;
redissonMultiLock = new RedissonMultiLock(locks);
boolean getLock = false;
try {
if (redissonMultiLock != null) {
//尝试获得锁
getLock = redissonMultiLock.tryLock();
if (!getLock) {
continue;
}
}
boolean isSuccess = tbDmsBasisCompanyConfigureService.deductNumber(1L, 1);
if (isSuccess) {
succeedCount++;
}
} catch (Exception e) {
continue;
} finally {
if (redissonMultiLock != null && getLock) {
redissonMultiLock.unlock();
}
}
}
return succeedCount;
}
})); }
int count = 0;
for (int i = 0; i < futures.size(); i++) {
try {
count += futures.get(i).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
//打印成功的数量
System.out.println(count); }
测试结果


可以发现数据正确
这个时候可能有疑惑 不是50个人每个人下单1000吗 怎么库存不是0 因为并发情况 锁互斥 大部分都提示系统繁忙请稍后重试了
解决方式2(不推荐)
手动开启事物
//防止全局配置了 所以这里定义sprnig 不托管事物
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public boolean deductNumber(Long id,int i){
//定义锁 库存id为id的数据
RLock[] locks = new RLock[]{redissonClient.getLock(String.valueOf(id))};
RedissonMultiLock redissonMultiLock = null;
redissonMultiLock = new RedissonMultiLock(locks);
boolean getLock = false;
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);//设置事物传播行为
TransactionStatus status = null;
try {
if (redissonMultiLock != null) {
//尝试获得锁
getLock = redissonMultiLock.tryLock();
if (!getLock) {
return false;//系统繁忙请重试
}
}
//开启事物 开启事物一定要提交或者回滚 不然又不可预知的问题
status = transactionManager.getTransaction(def);
RowMapper<Demo> rowMapper = new BeanPropertyRowMapper<Demo>(Demo.class);
//获得指定产品的库存
Demo demo= jdbcTemplate.queryForObject("select * from demo where id=?",rowMapper,id);
//判断库存是否充足
if(demo.getStockNumber()<i){
transactionManager.rollback(status);
return false;//库存不足 剩余库存demo.getStockNumber()
} //库存扣除
demo.setStockNumber(demo.getStockNumber()-i);
//持久化到数据
jdbcTemplate.update("update demo set stock_number=? where id=?",demo.getStockNumber(),demo.getId());
//提交事务
transactionManager.commit(status);
} catch (Exception e) {
return false;
} finally {
//释放锁
if (redissonMultiLock != null && getLock) {
redissonMultiLock.unlock();
}
//保险起见加一个这个代码 如果事物没提交回滚 执行回滚 一般都是我们代码问题
if(status!=null&&!status.isCompleted()){
transactionManager.rollback(status);
return false;
}
}
return true;
}
缺点
1.忘记提交或者回滚有不可预知问题 后面会分析
2.遇到其他事物方法调用这个方法 会有一致性问题 或者锁提前释放问题
不推荐
java陷阱之spring事物管理导致锁无效的更多相关文章
- java陷阱之spring事物未提交和回滚导致不可预知问题
案发现场 //防止全局配置了 所以这里定义sprnig 不托管事物 @Transactional(propagation = Propagation.NOT_SUPPORTED) public boo ...
- MyBatis6:MyBatis集成Spring事物管理(下篇)
前言 前一篇文章<MyBatis5:MyBatis集成Spring事物管理(上篇)>复习了MyBatis的基本使用以及使用Spring管理MyBatis的事物的做法,本文的目的是在这个的基 ...
- Spring事物管理--相关要点及配置事物管理器
事务的四大特征 1.原子性:一个事务中所有对数据库的操作是一个不可分割的操作序列,要么全做要么全不做 2.一致性:数据不会因为事务的执行而遭到破坏 3.隔离性:一个事物的执行,不受其他事务的干扰,即并 ...
- spring 事物管理没起到作用
今天在做项目的时候发现配置的spring 事物管理没起到作用.可是配置又是依据官网配置的,不可能会错.最后发现使mysql的问题 普通情况下,mysql会默认提供多种存储引擎,你能够通过以下的查看: ...
- Spring事物管理简介 (转)
一.事物1.什么是事物 事物指的是逻辑上的一组操作,这组操作要么全部成功,要么全部失败 2.事物的特性 原子性:事物是一个不可分割的工作单位,事物中的操作要么都发生,要么都不发生 一致性:事物前后数据 ...
- Java基础(spring事物和锁)
使用步骤: 步骤一.在spring配置文件中引入<tx:>命名空间<beans xmlns="http://www.springframework.org/schema/b ...
- 【JAVA】Spring 事物管理
在Spring事务管理中通过TransactionProxyFactoryBean配置事务信息,此类通过3个重要接口完成事务的配置及相关操作,分别是PlatformTransactio ...
- 集成Spring事物管理
什么是事物 事物是访问数据库的一个操作序列,数据库应用系统通过事物集来完成对数据库的存取.事物的正确执行使得数据库从一种状态转换为另一种状态. 事物必须服从ISO/IEC所制定的ACID原则.ACID ...
- SpringAOP和Spring事物管理
Spring AOP : Pointcut表达式: designators-指示器 wildcards-通配符 operators-操作符 wildcards: * -- 匹配任意数量的字符 + -- ...
随机推荐
- 【POJ 1011】 Sticks
[题目链接] http://poj.org/problem?id=1011 [算法] 深搜剪枝 首先我们枚举木棍的长度i,那么就有s/i根木棍,其中s为木棍长度的总和,朴素的做法就是对每种长度进行搜索 ...
- 【LuoguP2210 USACO】 Haywire
这种答案跟序列排列顺序有关的,n比较小的(稍微大一点的也可以),求最优解的,一般都可以随机化过 随机化不一定是模拟退火或是什么遗传蚁群 哪怕只是直接随机化一个序列,只要你随机的次数够多,它都能找到正解 ...
- 文字水平居中和垂直居中的CSS
首先选择一个需要显示文字的选择器,我这里选择的是微信小程序里面的<view>选择器,在其他语言(如html)的选择器里是一样的做法: <view class="btn-it ...
- CSS自定义消息提示
1.效果 2.源码 <%@ page contentType="text/html;charset=UTF-8" language="java" %> ...
- mysql数据库知识点总结
一.数据库的基本操作 --------------------------------------------------------------数据库的安装以后更新----------------- ...
- Hadoop学习笔记(一)Hadoop的单节点安装
要想深入学习Hadoop分布式文件系统,首先需要搭建Hadoop的实验环境,Hadoop有两种安装模式,即单节点集群模式安装(也称为伪分布式)和完全分布式模式安装,本节只介绍单节点模式的安装,参考官方 ...
- Windows 10 新功能
一.与 Cortana 集成的便笺 借助便笺,你可捕捉并保存绝妙创意或记录重要细节.便笺现已与 Cortana 集成,让你能够设置整个设备中的提醒. (一) 先来了解一下微软小娜Cortana. ...
- OAuth四种模式
授权码模式(authorization code)----适用于网站服务端去oauth服务端申请授权 简化模式(implicit)----没有服务端,js+html页面去oauth服务端申请授权 密码 ...
- html 图片翻转
var Lb = false; var Ub = false; function rotate(obj) { if (obj == "L") { if (Lb == false) ...
- jq遍历table 下的 td 添加类
<script> $('#btntb').click(function () { $('#tab tr').each(function (i) { // 遍历 tr $(this).chi ...