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

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个线程,来执行转账操作

@Testpublic 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转lisi100Thread t1 = new Thread(() ->{            accountService.transfer(zhangsanAccountId, lisiAccountId, money);});

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

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

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

//zhangsan转zhaoliu 50Thread 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();}

启动我们来看看结果

image.png

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

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

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}
我们来看看执行结果,测试代码就不展示了,和上面一样

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("账户余额不足,不允许转账");}}

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

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

@Configurationpublic class RedisLockConfiguration {

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

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

悲观锁和乐观锁_悲观锁和乐观锁处理并发操作相关推荐

  1. 公平锁非公平锁的实际使用_理解ReentrantLock的公平锁和非公平锁

    学习AQS的时候,了解到AQS依赖于内部的FIFO同步队列来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成一个Node对象并将其加入到同步队列,同时会阻塞当 ...

  2. iphone有锁和无锁是什么意思_科普|iPhone有锁和无锁的区别

    随着越来越多的人购买iPhone,很多用户在购买iPhone的时候难免会发现,为什么同一型号的iPhone,但是价格却相差很大,经过了解一问才知道是有锁机,那么有锁和无锁机有什么区别呢? 无锁iPho ...

  3. python gil锁存在的意义_对于Python的GIL锁理解

    GIL是什么 首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念.就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可 ...

  4. 华为应用锁退出立即锁_面试官:你说说互斥锁、自旋锁、读写锁、悲观锁、乐观锁的应用场景...

    前言 生活中用到的锁,用途都比较简单粗暴,上锁基本是为了防止外人进来.电动车被偷等等. 但生活中也不是没有 BUG 的,比如加锁的电动车在「广西 - 窃·格瓦拉」面前,锁就是形同虚设,只要他愿意,他就 ...

  5. 关抢占 自旋锁_互斥锁、自旋锁、读写锁、悲观锁、乐观锁的应用场景

    前言 生活中用到的锁,用途都比较简单粗暴,上锁基本是为了防止外人进来.电动车被偷等等. 但生活中也不是没有 BUG 的,比如加锁的电动车在「广西 - 窃·格瓦拉」面前,锁就是形同虚设,只要他愿意,他就 ...

  6. 悲观锁和乐观锁_面试必备之乐观锁与悲观锁

    何谓悲观锁与乐观锁 乐观锁对应于生活中乐观的人总是想着事情往好的方向发展,悲观锁对应于生活中悲观的人总是想着事情往坏的方向发展.这两种人各有优缺点,不能不以场景而定说一种人好于另外一种人. 大家可以点 ...

  7. 轻量级锁_一句话撸完重量级锁、自旋锁、轻量级锁、偏向锁、悲观、乐观锁等各种锁 不看后悔系列...

    重量级锁?自旋锁?自适应自旋锁?轻量级锁?偏向锁?悲观锁?乐观锁?执行一个方法咋这么辛苦,到处都是锁. 今天这篇文章,给大家普及下这些锁究竟是啥,他们的由来,他们之间有啥关系,有啥区别. 重量级锁 如 ...

  8. mysql锁的应用场景_浅谈Mysql共享锁、排他锁、悲观锁、乐观锁及其使用场景

    Mysql共享锁.排他锁.悲观锁.乐观锁及其使用场景 一.相关名词 |--表级锁(锁定整个表) |--页级锁(锁定一页) |--行级锁(锁定一行) |--共享锁(S锁,MyISAM 叫做读锁) |-- ...

  9. mysql悲观锁和乐观优缺点_乐观锁、悲观锁和MVCC各是什么?各自优缺点是什么?...

    在数据库的实际使用过程中,我们常常会遇到不希望数据被同时写或者读的情景,例如秒杀场景下,两个请求同时读到系统还有库存1个,然后又先后把库存更新为0,这时候就会出现超卖的情况,这时候货物的实际库存和我们 ...

  10. mysql悲观锁乐观锁定义_悲观锁乐观锁的定义

    悲观锁,正如其名,具有强烈的独占和排他特性,它指的是对数据被外界修改持保守态度.乐观锁机制采取了更加宽松的加锁机制,乐观锁是相对悲观锁而言,也是为了避免数据库幻读.业务处理时间过长等原因引起数据处理错 ...

最新文章

  1. struts.xml配置详解
  2. 广东海洋大学微型计算机考试,广东海洋大学2007-2008微型计算机原理及应用
  3. LINUX 下构建OpenGL ES 3.0
  4. 洛谷P1352 没有上司的舞会题解
  5. 【控制】如何入门现代控制理论
  6. UNITY2018 真机开启deepprofiling的操作
  7. OAuth2.0在项目中认证流程介绍
  8. 使用Prometheus和Grafana监视开放自由
  9. stl取出字符串中的字符_从C ++ STL中的字符串访问字符元素
  10. php增加mysql用户_mysql 增加用户
  11. 关于python中self
  12. 菜鸟学习笔记:Java提升篇5(IO流1——IO流的概念、字节流、字符流、缓冲流、转换流)
  13. python编写个人信息_1、纯python编写学生信息管理系统
  14. listview控件在php的使用方法,VBA窗体之ListView控件的基本应用 | VBA实例教程
  15. 软件项目管理:软件工具与开发环境相关知识介绍
  16. WAP在线浏览器大全
  17. 万能收钱码-多合一收款二维码原理及源码-支持支付宝、微信、QQ
  18. 【Android音视频开发】【007】SurfaceView实现H264播放器
  19. H5动画实现---过渡
  20. 计算广告4——用户增长

热门文章

  1. leetcode-114. Flatten Binary Tree to Linked List
  2. MyEclipse连接MySQL
  3. WP7应用开发笔记(8) IP输入框控件
  4. Win7中IIS7.0安装及ASP环境配置
  5. DotText源码阅读(7) --Pingback/TrackBack
  6. JS一维数组转化为三维数组有这个方法就够了
  7. 看不清的融资迷局 二线玩家字节跳动在打什么主意?
  8. Logcat打印调试信息
  9. 在eclipse中,怎么改变字体大小?
  10. Java二元运算和三元运算速度测试