悲观锁和乐观锁_悲观锁和乐观锁处理并发操作

本人在金融公司任职,今天来分享下关于转账的一些并发处理问题,这节内容,我们不聊实现原来,就单纯的看看如何实现
废话不多说,咱们直接开始,首先我会模拟一张转账表
如下图所示:

c80984157d7a142683e86663d48c9488.png

image.png

一张简单的账户表,有name,账户余额等等,接下来我将用三种锁的方式来实现下并发下的互相转账
一:悲观锁:
概念我就在这里不说了,很简单,直接上代码
接口层:

void transfer(Integer sourceId, Integer targetId, BigDecimal money);

实现层:

/**
* 转账操作(使用悲观锁的方式执行转账操作)
* source:转出账户
* target:转入专户
* money:转账金额
* @param sourceId
* @param targetId
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void transfer(Integer sourceId, Integer targetId, BigDecimal money) {
RyxAccount source;
RyxAccount target;
//处理死锁问题,每次从小到大执行
if (sourceId <= targetId){
source = getAccount(sourceId);
target = getAccount(targetId);
}else{
target = getAccount(targetId);
source = getAccount(sourceId);
}

if (source.getMoney().compareTo(money) >=0){
source.setMoney(source.getMoney().subtract(money));
target.setMoney(target.getMoney().add(money));

updateAccount(source);
updateAccount(target);
}else{
log.error("账户[{}]余额[{}]不足,不允许转账", source.getId(),source.getMoney());
}

}

private RyxAccount getAccount(Integer sourceId) {
return this.ryxAccountService.getRyxAccountByPrimaryKeyForUpdate(sourceId);
}

private void updateAccount(RyxAccount account) {
account.setUpdateTime(new Date());
this.ryxAccountService.updateByPrimaryKey(account, account.getId());
}

mapper层:

<select id="getRyxAccountByPrimaryKeyForUpdate" resultMap="base_result_map" >
select <include refid="base_column_list" /> from `ryx_account` where `id`=#{id} for update
</select>

测试代码:我同时启动5个线程,来执行转账操作

@Test
public void transferTest() throws InterruptedException {
Integer zhangsanAccountId = 315;
Integer lisiAccountId = 316;
Integer wangwuAccountId =317;
Integer zhaoliuAccountId = 318;

BigDecimal money = new BigDecimal(100);
BigDecimal money1 = new BigDecimal(50);
//zhangsan转lisi100
Thread t1 = new Thread(() ->{
accountService.transfer(zhangsanAccountId, lisiAccountId, money);
});

//lisi转wangwu100
Thread t2 = new Thread(() ->{
accountService.transfer(lisiAccountId, wangwuAccountId, money);
});

//wangwu转zhaoliu 100
Thread t3 = new Thread(() ->{
accountService.transfer(wangwuAccountId, zhaoliuAccountId, money);
});

//zhoaliu转zhangsan 100
Thread t4 = new Thread(() ->{
accountService.transfer(zhaoliuAccountId, zhangsanAccountId, money);
});

//zhangsan转zhaoliu 50
Thread t5 = new Thread(() ->{
accountService.transfer(zhangsanAccountId, zhaoliuAccountId, money1);
});


t1.start();
t2.start();
t3.start();
t4.start();
t5.start();

t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
}

启动我们来看看结果

71b1009372dfe81a906c18f64570c5a8.png

image.png

执行结果符合预期
悲观锁的主要逻辑就是在查询的时候使用select * ..... for update 语句,还有一点,子啊账户查询的时候,我们按照主键的顺序执行了排序后进行处理,否则会发生死锁,原理我们就先不介绍了,主要就是先看看如何处理

二:悲观锁:这个情况下,我就就需要在数据库中增加一个版本号,

df4ba18e1509ac312c4d19c0e5450603.png

image.png

接着看代码
接口层:

/**
* 乐观锁方式
* @param sourceId 转出账户
* @param targetId 转入账户
* @param money 转账金额
*/
void transferOptimistic(Integer sourceId, Integer targetId, BigDecimal money);

实现层:

/**
* 使用乐观锁执行
* @param sourceId
* @param targetId
* @param money
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void transferOptimistic(Integer sourceId, Integer targetId, BigDecimal money) {
RyxAccount source = getAccountOptimistic(sourceId);
RyxAccount target = getAccountOptimistic(targetId);


if (source.getMoney().compareTo(money) >=0){
source.setMoney(source.getMoney().subtract(money));
target.setMoney(target.getMoney().add(money));

// 先锁 id 较大的那行,避免死锁
int result1, result2;
if (source.getId() <=target.getId()){
result1 = updateOptimisticAccount(source,source.getVersion());
result2 = updateOptimisticAccount(target,target.getVersion());
}else{
result2 = updateOptimisticAccount(target,target.getVersion());
result1 = updateOptimisticAccount(source,source.getVersion());
}

if (result1 < 1 || result2 < 1) {
throw new RuntimeException("转账失败,重试中....");
} else {
log.info("转账成功");
}
}else{
log.error("账户[{}]余额[{}]不足,不允许转账", source.getId(),source.getMoney());
}

}
private int updateOptimisticAccount(RyxAccount account,Integer version) {
account.setUpdateTime(new Date());
return this.ryxAccountService.updateOptimisticByPrimaryKey(account, account.getId(),version);
}

mapper层:


UPDATE `ryx_account`
`name` = #{bean.name},
`money` = #{bean.money},
`createTime` = #{bean.createTime},
`updateTime` = #{bean.updateTime},
version = version +1
where `id`=#{id}
and version = #{bean.version}

主要执行的sql语句就是update set xxxx version = version+1 where version = #{bean.version}
我们来看看执行结果,测试代码就不展示了,和上面一样

2893bc1280aadb46dacd28b0a69bbee1.png

image.png

可以看到报错了,需要重试,所以如果你需要使用乐观锁的话需要,有重试机制,而且重试次数比较多,所以对于转账操作,就不适合使用
乐观锁去解决
三:分布式锁:
接下来,我们在用第三种方式处理一下,就是使用分布式锁,分布式锁可以用redis分布式锁,也可以使用zk做分布式锁,比较简单
我们就直接上代码吧
接口层:

/**
* 分布式锁方式
* @param sourceId 转出账户
* @param targetId 转入账户
* @param money 转账金额
*/
void transferDistributed(Integer sourceId, Integer targetId, BigDecimal money);

实现层:

/**
* 使用分布式锁执行
* @param sourceId
* @param targetId
* @param money
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void transferDistributed(Integer sourceId, Integer targetId, BigDecimal money) {
try {
accountLock.lock();
distributedAccount(sourceId,targetId,money);
} catch (Exception e) {
log.error(e.getMessage());
//throw new RuntimeException("错误啦");
} finally {
accountLock.unlock();
}
}

private void distributedAccount(Integer sourceId, Integer targetId, BigDecimal money) {
RyxAccount source;
RyxAccount target;

//解决死锁问题
if (sourceId <= targetId){
source = this.ryxAccountService.getRyxAccountByPrimaryKey(sourceId);
target = this.ryxAccountService.getRyxAccountByPrimaryKey(targetId);
}else{
target = this.ryxAccountService.getRyxAccountByPrimaryKey(targetId);
source = this.ryxAccountService.getRyxAccountByPrimaryKey(sourceId);
}

if (source.getMoney().compareTo(money) >=0){
source.setMoney(source.getMoney().subtract(money));
target.setMoney(target.getMoney().add(money));

updateAccount(source);
updateAccount(target);
}else{
log.error("账户[{}]向[{}]转账余额[{}]不足,不允许转账", source.getId(),target.getId(),source.getMoney());
throw new RuntimeException("账户余额不足,不允许转账");
}
}

@Override
public void afterPropertiesSet() throws Exception {
if (accountLock == null){
accountLock = this.redisLockRegistry.obtain("account-lock");
}
}

分布式锁的配置,可以参考我之前的笔记,这里在简单贴出代码

@Configuration
public class RedisLockConfiguration {

@Bean
public RedisLockRegistry redisLockRegistry(RedisConnectionFactory redisConnectionFactory) {
RedisLockRegistry redisLockRegistry = new RedisLockRegistry(redisConnectionFactory, "spring-cloud", 5000L);
return redisLockRegistry;
}

}
``
写下来还是比较简单,今天只是大概在代码实现方面做了介绍,`咩有涉及到原理,原理分析篇涉及到的内容比较多,等后续整理出来再分享
再说说我们公司用的方法,我锁在职的是金融公司,转账操作特别频发,而且每天几十个亿的资金也很正常
而我们公司在处理转账的逻辑中,涉及到并发的时候,就是使用的悲观锁的方式来处理的.
今天就分享到这里!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/261738.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

冰原服务器维护,怪猎OL9月20日0:00全区全服维护更新公告

亲爱的猎人&#xff1a;我们将于9月20日0:00对所有大区全部服务器进行更新维护&#xff0c;本次维护预计时间4小时。还请猎人们提早下线&#xff0c;避免不必要的损失。我们将视实际情况提前或延迟开服&#xff0c;感谢大家的支持!维护时间&#xff1a;2016年9月20日(周二)0:00…

属性类:Properties

在一个属性文件中保存了多个属性&#xff0c;每一个属性就是直接用字符串表示出来的"keyvalue对"&#xff0c;而如果想要轻松地操作这些属性文件中的属性&#xff0c;可以通过Properties类方便地完成。 <1>设置和取得属性 import java.util.Properties;// // F…

Js操作cookie

为什么80%的码农都做不了架构师&#xff1f;>>> 贴上一段js操作cookie的方法&#xff1a; //setCookie function setCookie(cname, cvalue, exdays) { var d new Date(); d.setTime(d.getTime() (exdays*24*60*60*1000)); //day var expires "expires…

Eclipse3.6.2 64位启动报“Failed to load the JNI shared library”错的解决方法

Eclipse3.6.2 64位解压后双击运行eclipse&#xff0c;报“Failed to load the JNI shared library”错误。 如下图所示&#xff1a; 原来我的jdk是以前旧32位系统安装jdk后&#xff0c;不支持Eclipse3.6.2 64位导致报错&#xff0c;无法正常运行。从官网上&#xff1a; http://…

一个长方体玻璃容器从里面量长宽_泰来包装分享:如何设计钢边箱里面中型木包装箱...

钢边箱里面的中型木包装箱是以木质材料为主制成的有一定刚性的包装容器&#xff0c;是产品运输包装的主要容器之一&#xff0c;也是我国出口商品使用非常广泛的一种包装&#xff0c;在轻工&#xff0c;机械&#xff0c;半导体等包装领域起着不可替代的重要作用。关于钢边箱里面…

uml 中的小人

平常画uml的时候&#xff0c;经常会用到Actor, 也没仔细思考过Actor的深刻内涵今天看了程序员11月的杂志&#xff0c;里面有篇文章叫<这个小人不简单>&#xff0c;文章中强调用例技术的不同之处在于发现了"卖"-需求是研究软件怎么好卖的技能。现在对uml有了更深…

net start zabbix agent 服务没有相应控制功能_一步到位,服务器监控就是这么简单...

对于运维的日常工作来说&#xff0c;服务器监控是必须且最基础的一项内容。在企业基础设施运维过程中&#xff0c;管理员必须能够掌握所有服务器的运行状况&#xff0c;以便及时发现问题&#xff0c;尽可能减少故障的发生。通常我们会借助一些监控的软件来获取每个服务器的基础…

发现在创建云服务器ecs实例的磁盘快照时_玩转ECS第7讲|ECS数据保护-数据备份新特性与最佳实践...

简介&#xff1a; 本文中&#xff0c;阿里云智能弹性计算专家余初武(悟元)将结合阿里云近期推出的数据备份新特性(快照极速备份、一致性快照组)来介绍云上环境如何做数据备份的最佳实践&#xff1b;适合需要构建云上架构的工程师&#xff0c;架构师和云上实施从业人员收看。关键…

二分法求方程的根_快速求解方程的根——二分法与牛顿迭代法

今天是周四高等数学专题的第7篇文章。之前的文章和大家聊了许多数学上的理论&#xff0c;今天和大家聊点有用的东西。我们都知道&#xff0c;工业上的很多问题经过抽象和建模之后&#xff0c;本质还是数学问题。而说到数学问题就离不开方程&#xff0c;在数学上我们可以用各种推…

关于android开发环境的创建

最近想暑假找个实习单位&#xff0c;想想java android方面的应该比c、C要好点&#xff0c;然后就想重操旧业学习android 大三的时候我学过一个学期的android知识。当时创建开发环境我很快就弄好了&#xff0c;但是环境创建险些让我崩溃。 环境搭建包括四步&#xff1a; 1、JDK安…

Android多种View动画:EasyAndroidAnimations

&#xfeff;&#xfeff;Android多种View动画&#xff1a;EasyAndroidAnimations EasyAndroidAnimations是Android的一个动画库&#xff0c;使用起来简单方便&#xff0c;EasyAndroidAnimations将一个Android View以各种形式的动画动起来。 其中如图&#xff1a; EasyAndroidA…

关于ubuntu无法启动nginx的问题

在ubuntu13.04上使用apt方式安装nginx发现无法启动nginx&#xff0c;也不报错 查看nginx运行状态&#xff0c;显示未启动 搜索无果&#xff0c;想起了原来的遇到的一个问题&#xff0c;那时候是安装了nginx和lighthttpd服务器&#xff0c;导致nginx无法启动的情况&#xff0c;提…

Oracle以SQL方式导出导入(转移)数据

为什么80%的码农都做不了架构师&#xff1f;>>> 导出源数据 源数据库为Oracle 9g使用SQL Developer导出数据库的表结构和数据&#xff0c;导出成sql文件。这里的源Oracle和目标Oracle的编码是否相同&#xff0c;如果表字段里有时间类型的那么还要注意两库的日期格式…

oppo售后解锁恢复工具.zip_OPPO手机4个不为人知的小技巧,全知道的竟然不到1%,令人唏嘘...

随着科技的发展速度加快&#xff0c;智能手机产品也越做越高端。手机里面也包含着很多很实用的技巧&#xff0c;却没有多少人知道&#xff0c;简直就是白白的浪费呀&#xff01;应用分屏一个很好用的功能&#xff0c;利用它我们可同时进行两种操作&#xff0c;比如&#xff1a;…

matlab中方波信号的谐波表示

matlab中方波信号的谐波表示 一.数学运算 二.matlab代码 t-7:0.001:7; %x(t)中t取值范围为【-7,7】 T11; T4; w2*pi/T; a02*T1/T; Ninput(请输入谐波数); Xta0*ones(1,length(t)); for k1:NXtXt2*a0*sinc(k*a0)*cos(k*w*t); end plot(t,Xt);三.运行结果 四。结论 很明显…

websocket onclose方法什么时候触发_WebSocket断开重连解决方案,心跳重连实践

WebSocket是前后端交互的长连接&#xff0c;服务器可以主动向客户端推送信息&#xff0c;客户端也可以主动向服务器发送信息&#xff0c;是真正的双向平等对话&#xff0c;属于服务器推送技术的一种。项目中&#xff0c;我们经常会使用WebSocket和服务器建立持久的连接。但是前…

matlab计算离散卷积

一.卷积的数学运算 &#xff08;1&#xff09;定义法 &#xff08;2&#xff09;图解法 &#xff08;3&#xff09;竖式乘法 二.matlab中计算离散卷积使用conv()函数 conv(a,b) 计算序列a与b的卷积 以上图中的习题为例 matlab代码如下&#xff1a; n1[ -2 -1 0 1 2 3 4 5]; …

Maven中使用本地JAR包

为什么80%的码农都做不了架构师&#xff1f;>>> 在Maven项目中使用本地JAR包有两种方法&#xff1a; 1、使用system scope <dependencies><dependency><groupId>org.richard</groupId><artifactId>my-jar</artifactId><ver…

折半查找法(二分查找法)

一.举例 二.算法时间复杂度 假设一共有n个元素 第一次折半元素个数变为n/2; 第二次折半元素个数变为n/4; 第三次折半元素个数变为n/8&#xff1b; 。。。。。 第k次折半元素个数变为n/2^k; 。。。。。 假设k次找到&#xff0c;即为n/2^k1&#xff1b; klog2(n); 三.函数实现 …

悬浮截图软件_Windows最好用截图工具,QQ第一,它第二

大家平时可能都有自己惯用截图工具&#xff0c;比如 Snipaste、PickPick、QQ 截图&#xff0c;还有 Windows 自带的 WinShiftS 截图快捷键等等。如果你不是工具控&#xff0c;那一般来说 QQ 截图和 Windows 快截键就是最简单方便的两个截图工具了。但是真要说简单方便&#xff…