Java并发学习三:银行转账的死锁问题解决及示例
Java并发学习系列文章:Java并发学习-博客专栏
今天在学习极客时间专栏:《Java并发编程实战》
从03 | 互斥锁(上):解决原子性问题
到06 | 用“等待-通知”机制优化循环等待
。
课程主要用银行转账作为解决死锁的例子。
在转账过程中需要2把锁,一个锁锁住转出账户的余额,一把锁锁住转入账户的余额。但加锁的先后顺序会使程序产生死锁。
有以下这四个条件都发生时才会出现死锁:
1.互斥,共享资源 X 和 Y 只能被一个线程占用;
2.占有且等待,线程 T1 已经取得共享资源 X,在等待共享资源 Y 的时候,不释放共享资源 X;
3.不可抢占,其他线程不能强行抢占线程 T1 占有的资源;
4.循环等待,线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占有的资源,就是循环等待。
我们选择破坏占有且等待
这个条件使死锁失效。“同时申请”这个操作是一个临界区,我们也需要一个角色(Java 里面的类)来管理这个临界区,我们就把这个角色定为 Allocator。它有两个重要功能,分别是:同时申请资源 apply() 和同时释放资源 free()。账户 Account 类里面持有一个 Allocator 的单例(必须是单例,只能由一个人来分配资源)
现在有了另外一个问题,获取锁失败的线程应该怎么做,常见的做法有:
1.死循环尝试获取锁
2.轮询
3.进入阻塞状态,等待通知。
最好的办法自然是进入阻塞状态,等待通知
。Java提供函数wait
,notify
,notifyAll
。
所以完善后的完整测试代码如下:
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadLocalRandom;
import static java.lang.System.exit;//相当于临界区,规避死锁
class Allocator {//单例设计模式的饿汉式static private final Allocator instance = new Allocator();private Set<Object> res = new HashSet<>();public static Allocator getInstance() {return instance;}public synchronized void getLock(Object from, Object to) {while (res.contains(from) || res.contains(to)) {try {//将不满足条件的线程阻塞,加入等待的队列wait();} catch (InterruptedException e) {e.printStackTrace();}}res.add(from);res.add(to);}public synchronized void releaseLock(Object from, Object to) {res.remove(from);res.remove(to);//唤醒队列中所有等待的线程notifyAll();}
}class Account {private Integer balance;private Integer id;Account(Integer id) {this.id = id;balance = 100;}public Integer getBalance() {return balance;}public void setBalance(Integer balance) {this.balance = balance;}@Overridepublic String toString() {return "账号id " + id.toString() + " 余额 ¥" + balance.toString() + " ";}//转账public void transactionToTarget(Integer money, Account target) {//获取锁Allocator.getInstance().getLock(this, target);System.out.println("转账开始--转出账号:" + this.toString() + "|转入账号:" + target.toString());if (this.balance - money >= 0) {this.balance -= money;target.setBalance(target.getBalance() + money);System.out.println("转账成功--转出账号:" + this.toString() + "|转入账号:" + target.toString());} else {System.out.println("转账失败--转出账号:" + this.toString() + "|想转金额:¥" + money.toString());}//释放锁Allocator.getInstance().releaseLock(this, target);}
}public class Test {static private List<Account> testAccount = new ArrayList<>();static final int testAccountNumber = 100;static final int testThreadNumber = 1000;static final CountDownLatch countDownLatch = new CountDownLatch(testThreadNumber);static public void test() {testAccount.get(ThreadLocalRandom.current().nextInt(testAccountNumber)).transactionToTarget(ThreadLocalRandom.current().nextInt(100),testAccount.get(ThreadLocalRandom.current().nextInt(testAccountNumber)));countDownLatch.countDown();}public static void main(String[] args) throws InterruptedException {for (int i = 0; i < testAccountNumber; i++) {testAccount.add(new Account(i));}for (int i = 0; i < testThreadNumber; i++) {new Thread(Test::test).start();}countDownLatch.await();long totalNumber = 0;for (Account i : testAccount) {if (i.getBalance() < 0) {System.out.println("余额小于0: " + i.toString());exit(-1);}totalNumber += i.getBalance();}System.out.println("总余额: " + totalNumber);}
}
Java并发学习三:银行转账的死锁问题解决及示例相关推荐
- Java并发学习二:编译优化带来的有序性问题导致的并发Bug
Java并发学习系列文章:Java并发学习-博客专栏 今天在学习极客时间专栏:<Java并发编程实战> 第一讲01 | 可见性.原子性和有序性问题:并发编程Bug的源头中提到: 编译器及解 ...
- Java并发学习一:CPU缓存导致的可见性问题带来的并发Bug
Java并发学习系列文章:Java并发学习-博客专栏 今天在学习极客时间专栏:<Java并发编程实战> 第一讲01 | 可见性.原子性和有序性问题:并发编程Bug的源头中提到了: 多核时代 ...
- Java并发学习之玩转线程池
2019独角兽企业重金招聘Python工程师标准>>> 线程池的使用姿势 基本上实际的项目不可能离开线程池,只是看你有没有注意到罢了 作为以业务需求为驱动,最顺溜的是写if-else ...
- Java多线程学习之路(四)---死锁(DeadLock)
Java多线程学习之路(四)-死锁(DeadLock) 1.定义 死锁就是多个线程在竞争共享资源的时候,相互阻塞,不能脱身的状态(个人理解).其实死锁一定程度上可以看成一个死循环. 举个现实生活中的例 ...
- Java并发学习----大三仔(自己归纳的有错误请指正)
并发知识学习 三要素 1.原子性.即一个操作要么不做,要么做完,不存在做一半这种情况 2.可见性 .即一个变量被一个线程修改后,其他线程能够立即得知更改后的变量值 3.顺序性.即一连串操作在线程内部看 ...
- 20W字纯手打Java并发学习笔记,助力你金三银四,决战春招,必进大厂
假如阿里给了你这个机会,你却卡在三面,你会不会懊恼? 假如阿里真的让你通过,只需要你把这一块技能的底层原理摸透,你学不学? 我有一个朋友,他小厂背景.15年毕业.普通学校,这看起来确实没什么战斗力,但 ...
- 17、Java并发性和多线程-避免死锁
以下内容转自http://ifeve.com/deadlock-prevention/: 在有些情况下死锁是可以避免的.本文将展示三种用于避免死锁的技术: 加锁顺序 当多个线程需要相同的一些锁,但是按 ...
- 面试经验|春招在即,时间宝贵,这一定是最近的 Java 并发学习路线
多线程这部分内容确实比较高深而且每个知识点之间比较零散,让人摸不着头脑,不知道该从哪里下手.而且对于大部分学生群体来讲,很少有机会接触到高并发这方面的真实场景,平常自己敲代码也基本不会用到,所以也导致 ...
- java闭锁_【Java并发编程三】闭锁
1.什么是闭锁? 闭锁(latch)是一种Synchronizer(Synchronizer:是一个对象,它根据本身的状态调节线程的控制流.常见类型的Synchronizer包括信号量.关卡和闭锁). ...
最新文章
- vmware 报错解决:The system cannot find the path specified.
- 腾讯130公顷的深圳新总部了解一下,大小相当于曼哈顿城
- open(/dev/ietctl, O_RDWR) 参数含义
- 装了虚拟机,但是没有虚拟网卡vmnet0 vmnet1 vmnet8
- Hexo瞎折腾系列(8) - 添加评论系统
- 线程共享的环境包括:进程代码段、进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯)、进程打开的文件描述符、信号的处理器、进程的当前目录和进程用户ID与进程组ID。 进程拥有这
- 数据分析常用功能:巧用门户制作功能,让多个报表一览无余
- Go语言在扫码支付系统中的成功实践
- 82 个代码案例实践,带你学好 Python 机器学习
- java 读取raw文件_Android 读取assets和raw文件内容实例代码
- c语言实现软件锁相环,锁相环系统及锁相环系统的实现方法技术方案
- Leetcode-Maximum Subarray
- POJ 2396 有上下界的可行流
- atitit.j2ee 1.5 1.6 的不同跟 Servlet 3.0新特性总结
- 麦子学院字符设备驱动201126
- TAPD项目管理:工作流自动化最佳实践
- html java实训心得,学习中关于HTML的总结与一些心得
- 含论文基于SSH超市进销存管理系统【数据库设计、源码、开题报告】
- 单词串联记忆 - 故事 - Party上浪漫的事后传
- oracle执行计划time单位,Oracle中查看执行计划
热门文章
- Linux(CentOS 7_x64位)系统下安装Xmgrace
- 关于自注意力机制的思考
- 第二十四课.循环神经网络RNN
- linux平台的实验描述,基于LINUX的操作系统实验平台的设计与实现
- php写网页6,基于ThinkPHP6+AdminLTE框架开发的响应式企业网站CMS系统PHP源码,ThinkPHP6开发的后台权限管理系统...
- mysql 事务_MySQL事务
- eclipse可以写前端吗_Python 竟然也可以写网页前端了!
- 在线作图|如何绘制一个好看的堆叠柱状图
- 微生物组-宏基因组分析专题研讨会(2022.5)
- python使用matplotlib可视化subplots子图、subplots绘制子图、子图之间有重叠问题、使用subplots_adjust函数合理设置子图之间的水平和垂直距离