java 关于高并发下的银行转账问题 |
您所在的位置:网站首页 › 如何解决多线程并发问题 › java 关于高并发下的银行转账问题 |
在转账操作中,一致性必须要保证的,转账的前后,各个账户的金额必须符合算术一致性
如何保证一致性? 引入事务机制是肯定的,但是这就够了么,下面我们对其进行测试
场景: 小明账户中有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修饰符,使用悲观锁 在查询语句中添加悲观锁: 这里将数据量增加50倍测试: 测试内容 测试 循环50次执行以下操作: 同时开启900个线程 其中300个线程: 小明向小红转账10元 其中300个线程:小强向小红转账10元 其中300个线程:小红自己存入1元
数据库初始值: 期望值:
测试开始,转账中....... 测试结果: 用时13分钟 测试成功
总结: 对于数据库的多线程操作,需要加数据库级别的锁,悲观锁可以实现此功能,稍后测试乐观锁... |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |