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

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

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,一经查实,立即删除!

相关文章

C++中string与int\double等互转

double转为string (int\float等类似) #include <string>#inlcude <sstream>intmain(){doubled123.456; stringstr;   stringstream ss;   ss<<d;   ss>>str;}string转为double(int\float等类似) #include <string>#inlcude <sstream&…

冰原服务器维护,怪猎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…

C#基础之如何判断两个文件内容是否相同

使用System.security.Cryptography.HashAlgorithm类为每个文件生成一个哈希码&#xff0c;然后比较两个哈希码是否相同。 该哈希算法为一个文件生成一个小的二进制“指纹”&#xff0c;从统计学的角度来看&#xff0c;不同的文件不可能生成相同的哈希码 要生成一个哈希码&#…

python map lambda 分割字符串_python六剑客:map()、lambda()、filter()、reduce()、推导类表、切片...

一&#xff1a;map():映射map()函数在python2和python3中是区别的python2中的定义&#xff1a;映射后&#xff0c;返回一个列表>>> help(map)Help on built-in function map in module __builtin__:map(...)map(function, sequence[, sequence, ...]) -> listRetur…

WebDAV方式访问Exchange 2003收件箱程序

publicDataTable GetUnReadMail() { stringurl "http://ip/exchange/"; //指定Exchange服务器地址 System.Net.HttpWebRequest Request; System.Net.WebResponse Response; System.Net.CredentialCache MyCredentialCache; stringstrUserName "admi…

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…

网站主机和服务器选哪个,虚拟主机和服务器 你的网站该选择哪一个?

原标题&#xff1a;虚拟主机和服务器 你的网站该选择哪一个&#xff1f;大家在平时使用虚拟主机的时候可能并不了解他的原理&#xff0c;更是分不清楚虚拟主机和服务器之间的区别&#xff0c;不知道自己的网站该如何选择。虚拟主机是通过软件技术将一台服务器分成多个独立WEB发…

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有了更深…

快速上手Ubuntu搭建Python编程环境

#Ubuntu 14.04 LTS已经预装Python2/3&#xff0c;可以满足基本的编程需求。#让系统支持aptitude命令sudo apt-get install aptitude -y#将系统升级到最新。sudo aptitude -y upgrade#若需导入turtle来完成一些程序&#xff0c;则需要补充安装python-tk库&#xff1a;#为Python3…

SQL SERVER 事务处理

<1>.事务的概念 事务是一个不可分割的工作逻辑单元。<2>.为什么使用事物 可以防止错误的发生。<3>.事物的作用 要么所有的操作全部完成&#xff0c;要么所有操作都不执行。<4>.什么是事物 事务是单个的工作单元 …

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

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

【原题】【noip 2003 T2】【动态规划】加分二叉树

问题 描述 Description 设一个n个节点的二叉树tree的中序遍历为&#xff08;l,2,3,…,n&#xff09;&#xff0c;其中数字1,2,3,…,n为节点编号。每个节点都有一个分数&#xff08;均为正整数&#xff09;&#xff0c;记第i个节点的分数为di&#xff0c;tree及它的每个子树都有…

HRegionServer上工作组件汇总

HRegionServer上工作组件汇总&#xff0c;近期推出&#xff01;

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

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

用Ant编译Flex项目的几点注意事项

1. 往mxmlc中用define添加编译参数的时候特别要注意一点&#xff0c;字符串一定要包含在单引号中。 下面的写法是无法通过的&#xff1a; <mxmlc ...><define name"NS::NAME" value"Flex in air"/> </mxmlc>下面的写法才是正确的&#x…

链表打印从尾到头打印链表

在本文中,我们主要介绍链表打印的内容,自我感觉有个不错的建议和大家分享下 每日一道理 生命不是一篇"文摘"&#xff0c;不接受平淡&#xff0c;只收藏精彩。她是一个完整的过程&#xff0c;是一个"连载"&#xff0c;无论成功还是失败&#xff0c;她都不会…

1、EJB基本概念及运行环境

1、EJB是什么&#xff1f; EJB是一个用于分布式业务应用的标准服务端组件模型。采用EJB构架编写的应用是可伸缩的、事务性的、多用户安全的。采用EJB编写的应用可以部署在任何支持EJB规范的服务器平台上&#xff0c;例如&#xff1a;jboss、weblogic。 2、EJB是用来干什么的&am…