java 关于高并发下的银行转账问题

您所在的位置:网站首页 如何解决多线程并发问题 java 关于高并发下的银行转账问题

java 关于高并发下的银行转账问题

2023-08-05 16:33| 来源: 网络整理| 查看: 265

在转账操作中,一致性必须要保证的,转账的前后,各个账户的金额必须符合算术一致性 如何保证一致性? 引入事务机制是肯定的,但是这就够了么,下面我们对其进行测试

   

场景:

            小明账户中有10000元,小强账户中有10000元,小红账户中有0元

            有三个操作同时进行:小明给小红转账10元 ,小强给小红转账10元,小红自己往账户存入1元

            为了模拟高并发,我这里将上述操作复制300份,每份300个线程中同时执行

 

 

测试一:

代码如下:(测试代码,细节的判断我就省略了)

这里仅仅引入了事务机制,事务的隔离级别为 REPEATABLE_READ

/** * 转账10元 */ @Transactional @Override public void testTransfer(Integer idFrom, Integer idTo) { //SQL语句 : select * from users where id=? Users from = usersMapper.selectById(idFrom); Users to = usersMapper.selectById(idTo); System.out.println(from.getUserName() + "向" + to.getUserName() + "转账 "); //这里加上10毫秒的延时,用于测试 delay(10L); //转账方减10元 from.setMoney(from.getMoney() - 10); usersMapper.updateByPrimaryKey(from); //这里加上10毫秒的延时,用于测试 delay(10L); //收款方增加10元 to.setMoney(to.getMoney() + 10); usersMapper.updateByPrimaryKey(to); System.out.println(from.getUserName() + "向" + to.getUserName() + "转账完毕---"); } /** * 存入1元 * * @param id */ @Override @Transactional public void testDeposit(Integer id) { System.out.println("小红自己存钱"); Users user = usersMapper.selectById(id); //延时10ms delay(10L); //存入1元 user.setMoney(user.getMoney() + 1); usersMapper.updateByPrimaryKey(user); System.out.println("小红自己存钱 完毕+++"); } /** * 延时 */ public void delay(Long ms) { try { Thread.sleep(ms); } catch (InterruptedException e) { e.printStackTrace(); } }

测试内容     测试         同时开启900个线程             其中300个线程: 小明向小红转账10元             其中300个线程:小强向小红转账10元             其中300个线程:小红自己存入1元

    数据库初始值:

  期望值:

 

测试开始,转账中.......

3次测试的结果为:

分析:很明显这里是由于多线程操作同一数据遇到的线程同步安全问题,怎么解决呢?第一时间我想到的是加个同步锁:synchronization,加锁后进行第二次测试

 

测试二:

代码如下: 在测试一的基础上,加了同步锁synchronized 

/** * 转账10元 */ @Transactional @Override public synchronized void testTransfer(Integer idFrom, Integer idTo) { //SQL语句 : select * from users where id=? Users from = usersMapper.selectById(idFrom); Users to = usersMapper.selectById(idTo); System.out.println(from.getUserName() + "向" + to.getUserName() + "转账 "); //这里加上10毫秒的延时,用于测试 delay(3L); //转账方减10元 from.setMoney(from.getMoney() - 10); usersMapper.updateByPrimaryKey(from); //这里加上10毫秒的延时,用于测试 delay(3L); //收款方增加10元 to.setMoney(to.getMoney() + 10); usersMapper.updateByPrimaryKey(to); System.out.println(from.getUserName() + "向" + to.getUserName() + "转账完毕---"); } /** * 存入1元 * * @param id */ @Override @Transactional public synchronized void testDeposit(Integer id) { System.out.println("小红自己存钱"); Users user = usersMapper.selectById(id); //延时10ms delay(3L); //存入1元 user.setMoney(user.getMoney() + 1); usersMapper.updateByPrimaryKey(user); System.out.println("小红自己存钱 完毕+++"); } /** * 延时 */ public void delay(Long ms) { try { Thread.sleep(ms); } catch (InterruptedException e) { e.printStackTrace(); } }

测试内容     测试         同时开启900个线程             其中300个线程: 小明向小红转账10元             其中300个线程:小强向小红转账10元             其中300个线程:小红自己存入1元

    数据库初始值:

  期望值:

 

测试开始,转账中.......

测试结果:

分析:用时增加到17秒,结果还是会出错,这时为啥呢? 由于上面的同步锁只锁了自己的方法,但是其他方法还是可以操作数据库的,有没有一种方法是给数据库的某条数据上锁呢,有的,这里可以使用悲观锁:顾名思义,就是很悲观的锁,事务在查询数据时,总是悲观的认为其它事务会操作这条数据,所以上了锁。直到事务commit才会释放。期间会阻塞其他事务想要给这条数据上锁。下面进行悲观锁测试。

 

测试3:

这里去掉了synchronization修饰符,使用悲观锁

在查询语句中添加悲观锁:

/** * 转账10元 */ @Transactional @Override public void testTransfer(Integer idFrom, Integer idTo) { //SQL语句 : select * from users where id=? for updata Users from = usersMapper.selectByIdForUpdate(idFrom); Users to = usersMapper.selectByIdForUpdate(idTo); System.out.println(from.getUserName() + "向" + to.getUserName() + "转账 "); //这里加上10毫秒的延时,用于测试 delay(10L); //转账方减10元 from.setMoney(from.getMoney() - 10); usersMapper.updateByPrimaryKey(from); //这里加上10毫秒的延时,用于测试 delay(10L); //收款方增加10元 to.setMoney(to.getMoney() + 10); usersMapper.updateByPrimaryKey(to); System.out.println(from.getUserName() + "向" + to.getUserName() + "转账完毕---"); } /** * 存入1元 * * @param id */ @Override @Transactional public void testDeposit(Integer id) { System.out.println("小红自己存钱"); Users user = usersMapper.selectByIdForUpdate(id); //延时10ms delay(10L); //存入1元 user.setMoney(user.getMoney() + 1); usersMapper.updateByPrimaryKey(user); System.out.println("小红自己存钱 完毕+++"); } /** * 延时 */ public void delay(Long ms) { try { Thread.sleep(ms); } catch (InterruptedException e) { e.printStackTrace(); } }

这里将数据量增加50倍测试:

测试内容     测试

循环50次执行以下操作:         同时开启900个线程             其中300个线程: 小明向小红转账10元             其中300个线程:小强向小红转账10元             其中300个线程:小红自己存入1元

 

    数据库初始值:

  期望值:

 

测试开始,转账中.......

测试结果:

用时13分钟

测试成功

 

总结:

对于数据库的多线程操作,需要加数据库级别的锁,悲观锁可以实现此功能,稍后测试乐观锁...


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3