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提供函数waitnotifynotifyAll


所以完善后的完整测试代码如下:

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并发学习三:银行转账的死锁问题解决及示例相关推荐

  1. Java并发学习二:编译优化带来的有序性问题导致的并发Bug

    Java并发学习系列文章:Java并发学习-博客专栏 今天在学习极客时间专栏:<Java并发编程实战> 第一讲01 | 可见性.原子性和有序性问题:并发编程Bug的源头中提到: 编译器及解 ...

  2. Java并发学习一:CPU缓存导致的可见性问题带来的并发Bug

    Java并发学习系列文章:Java并发学习-博客专栏 今天在学习极客时间专栏:<Java并发编程实战> 第一讲01 | 可见性.原子性和有序性问题:并发编程Bug的源头中提到了: 多核时代 ...

  3. Java并发学习之玩转线程池

    2019独角兽企业重金招聘Python工程师标准>>> 线程池的使用姿势 基本上实际的项目不可能离开线程池,只是看你有没有注意到罢了 作为以业务需求为驱动,最顺溜的是写if-else ...

  4. Java多线程学习之路(四)---死锁(DeadLock)

    Java多线程学习之路(四)-死锁(DeadLock) 1.定义 死锁就是多个线程在竞争共享资源的时候,相互阻塞,不能脱身的状态(个人理解).其实死锁一定程度上可以看成一个死循环. 举个现实生活中的例 ...

  5. Java并发学习----大三仔(自己归纳的有错误请指正)

    并发知识学习 三要素 1.原子性.即一个操作要么不做,要么做完,不存在做一半这种情况 2.可见性 .即一个变量被一个线程修改后,其他线程能够立即得知更改后的变量值 3.顺序性.即一连串操作在线程内部看 ...

  6. 20W字纯手打Java并发学习笔记,助力你金三银四,决战春招,必进大厂

    假如阿里给了你这个机会,你却卡在三面,你会不会懊恼? 假如阿里真的让你通过,只需要你把这一块技能的底层原理摸透,你学不学? 我有一个朋友,他小厂背景.15年毕业.普通学校,这看起来确实没什么战斗力,但 ...

  7. 17、Java并发性和多线程-避免死锁

    以下内容转自http://ifeve.com/deadlock-prevention/: 在有些情况下死锁是可以避免的.本文将展示三种用于避免死锁的技术: 加锁顺序 当多个线程需要相同的一些锁,但是按 ...

  8. 面试经验|春招在即,时间宝贵,这一定是最近的 Java 并发学习路线

    多线程这部分内容确实比较高深而且每个知识点之间比较零散,让人摸不着头脑,不知道该从哪里下手.而且对于大部分学生群体来讲,很少有机会接触到高并发这方面的真实场景,平常自己敲代码也基本不会用到,所以也导致 ...

  9. java闭锁_【Java并发编程三】闭锁

    1.什么是闭锁? 闭锁(latch)是一种Synchronizer(Synchronizer:是一个对象,它根据本身的状态调节线程的控制流.常见类型的Synchronizer包括信号量.关卡和闭锁). ...

最新文章

  1. vmware 报错解决:The system cannot find the path specified.
  2. 腾讯130公顷的深圳新总部了解一下,大小相当于曼哈顿城
  3. open(/dev/ietctl, O_RDWR) 参数含义
  4. 装了虚拟机,但是没有虚拟网卡vmnet0 vmnet1 vmnet8
  5. Hexo瞎折腾系列(8) - 添加评论系统
  6. 线程共享的环境包括:进程代码段、进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯)、进程打开的文件描述符、信号的处理器、进程的当前目录和进程用户ID与进程组ID。 进程拥有这
  7. 数据分析常用功能:巧用门户制作功能,让多个报表一览无余
  8. Go语言在扫码支付系统中的成功实践
  9. 82 个代码案例实践,带你学好 Python 机器学习
  10. java 读取raw文件_Android 读取assets和raw文件内容实例代码
  11. c语言实现软件锁相环,锁相环系统及锁相环系统的实现方法技术方案
  12. Leetcode-Maximum Subarray
  13. POJ 2396 有上下界的可行流
  14. atitit.j2ee 1.5 1.6 的不同跟 Servlet 3.0新特性总结
  15. 麦子学院字符设备驱动201126
  16. TAPD项目管理:工作流自动化最佳实践
  17. html java实训心得,学习中关于HTML的总结与一些心得
  18. 含论文基于SSH超市进销存管理系统【数据库设计、源码、开题报告】
  19. 单词串联记忆 - 故事 - Party上浪漫的事后传
  20. oracle执行计划time单位,Oracle中查看执行计划

热门文章

  1. Linux(CentOS 7_x64位)系统下安装Xmgrace
  2. 关于自注意力机制的思考
  3. 第二十四课.循环神经网络RNN
  4. linux平台的实验描述,基于LINUX的操作系统实验平台的设计与实现
  5. php写网页6,基于ThinkPHP6+AdminLTE框架开发的响应式企业网站CMS系统PHP源码,ThinkPHP6开发的后台权限管理系统...
  6. mysql 事务_MySQL事务
  7. eclipse可以写前端吗_Python 竟然也可以写网页前端了!
  8. 在线作图|如何绘制一个好看的堆叠柱状图
  9. 微生物组-宏基因组分析专题研讨会(2022.5)
  10. python使用matplotlib可视化subplots子图、subplots绘制子图、子图之间有重叠问题、使用subplots_adjust函数合理设置子图之间的水平和垂直距离