文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

一篇带给你多线程死锁详解

2024-12-03 08:29

关注

经典的 "哲学家进餐" 问题很好地描述了死锁状况:

死锁:每个人都立即抓住自己左边的筷子,然后等待自己右边的筷子空出来,但同时又不放下已经拿到的筷子,形成一种相互等待的状态。饥饿:哲学家们都同时想吃饭,同时拿起左手边筷子,但是发现右边没有筷子,于是哲学家又同时放下左手边筷子,然后大家发现又有筷子了,又同时开始拿起左手边筷子,又同时放下,然后反复进行。

 [[390005]]

在线程A持有锁L并想获得锁M的同时,线程B持有锁M并尝试获得锁L,那么这两个线程将永远地等待下去,这种情况就是死锁形式(或者称为"抱死")


二、死锁的四个必要条件

三、死锁实例

  1.  
  2.  
  3. public class DeadLock implements Runnable { 
  4.  
  5. public int flag = 1; 
  6.  
  7. //静态对象是类的所有对象共享的 
  8.  
  9. private static Object o1 = new Object(), o2 = new Object(); 
  10.  
  11. @Override 
  12.  
  13. public void run() { 
  14.  
  15. System.out.println("flag:{}"+flag); 
  16.  
  17. if (flag == 1) { //先锁o1,再对o2加锁,环路等待条件 
  18.  
  19. synchronized (o1) { 
  20.  
  21. try { 
  22.  
  23. Thread.sleep(500); 
  24.  
  25. } catch (Exception e) { 
  26.  
  27. e.printStackTrace(); 
  28.  
  29.  
  30. synchronized (o2) { 
  31.  
  32. System.out.println("1"); 
  33.  
  34.  
  35.  
  36.  
  37. if (flag == 0) {//先锁o2,在锁01 
  38.  
  39. synchronized (o2) { 
  40.  
  41. try { 
  42.  
  43. Thread.sleep(500); 
  44.  
  45. } catch (Exception e) { 
  46.  
  47. e.printStackTrace(); 
  48.  
  49.  
  50. synchronized (o1) { 
  51.  
  52. System.out.println("0"); 
  53.  
  54.  
  55.  
  56.  
  57.  
  58. public static void main(String[] args) { 
  59.  
  60. DeadLock td1 = new DeadLock(); 
  61.  
  62. DeadLock td2 = new DeadLock(); 
  63.  
  64. td1.flag = 1; 
  65.  
  66. td2.flag = 0; 
  67.  
  68. //td1,td2都处于可执行状态,但JVM线程调度先执行哪个线程是不确定的。 
  69.  
  70. //td2的run()可能在td1的run()之前运行 
  71.  
  72. new Thread(td1).start(); 
  73.  
  74. new Thread(td2).start(); 
  75.  
  76.  

当DeadLock 类的对象flag=1时(td1),先锁定o1,睡眠500毫秒2、而td1在睡眠的时候另一个flag==0的对象(td2)线程启动,先锁定o2,睡眠500毫秒3、td1睡眠结束后需要锁定o2才能继续执行,而此时o2已被td2锁定;4、td2睡眠结束后需要锁定o1才能继续执行,而此时o1已被td1锁定;5、td1、td2相互等待,都需要得到对方锁定的资源才能继续执行,从而死锁。

 

动态锁顺序死锁:

  1. // 资金转账到账号 
  2.  
  3. public static void transferMoney(Account fromAccount, 
  4.  
  5. Account toAccount, 
  6.  
  7. DollarAmount amount) 
  8.  
  9. throws InsufficientFundsException { 
  10.  
  11. // 锁定汇款者的账户 
  12.  
  13. synchronized (fromAccount) { 
  14.  
  15. // 锁定到账者的账户 
  16.  
  17. synchronized (toAccount) { 
  18.  
  19. // 判断账户的余额不能为负数 
  20.  
  21. if (fromAccount.getBalance().compareTo(amount) < 0) { 
  22.  
  23. throw new InsufficientFundsException(); 
  24.  
  25. else { 
  26.  
  27. // 汇款者的账户减钱 
  28.  
  29. fromAccount.debit(amount); 
  30.  
  31. // 到账者的账户增钱 
  32.  
  33. toAccount.credit(amount); 
  34.  
  35.  
  36.  
  37.  

上面的代码看起来都是按照相同的顺序来获得锁的,按道理来说是没有问题,但是上述代码中上锁的顺序取决于传递给 transferMoney()的参数顺序,而这些参数顺序又取决于外部的输入

当一组java线程发生死锁的时候,那么这些线程永远不能再使用了,根据线程完成工作的不同,可能会造成应用程序的完全停止,或者某个特定的子系统不能再使用了,或者是性能降低,这个时候恢复应用程序的唯一方式就是中止并重启它,死锁造成的影响很少会立即显现出来,如果一个类发生死锁,并不意味着每次都会发生死锁,而只是表示有可能,当死锁出现的时候,往往是在最糟糕的时候——在高负载的情况下。

四、死锁的避免与检测

4.1 预防死锁

4.2 设置加锁顺序

如果两个线程(A和B),当A线程已经锁住了Z,而又去尝试锁住X,而X已经被线程B锁住,线程A和线程B分别持有对应的锁,而又去争夺其他一个锁(尝试锁住另一个线程已经锁住的锁)的时候,就会发生死锁,如下图:

两个线程试图以不同的顺序来获得相同的锁,如果按照相同的顺序来请求锁,那么就不会出现循环的加锁依赖性,因此也就不会产生死锁,每个需要锁Z和锁X的线程都以相同的顺序来获取Z和X,那么就不会发生死锁了,如下图所示:


这样死锁就永远不会发生。针对两个特定的锁,可以尝试按照锁对象的hashCode值大小的顺序,分别获得两个锁,这样锁总是会以特定的顺序获得锁,我们通过设置锁的顺序,来防止死锁的发生,在这里我们使用 System.identityHashCode方法来定义锁的顺序,这个方法将返回由Obejct.hashCode 返回的值,这样就可以消除死锁发生的可能性。

  1. public class DeadLockExample3 { 
  2.  
  3. // 加时赛锁,在极少数情况下,如果两个hash值相等,使用这个锁进行加锁 
  4.  
  5. private static final Object tieLock = new Object(); 
  6.  
  7. public void transferMoney(final Account fromAcct, 
  8.  
  9. final Account toAcct, 
  10.  
  11. final DollarAmount amount) 
  12.  
  13. throws InsufficientFundsException { 
  14.  
  15. class Helper { 
  16.  
  17. public void transfer() throws InsufficientFundsException { 
  18.  
  19. if (fromAcct.getBalance().compareTo(amount) < 0) 
  20.  
  21. throw new InsufficientFundsException(); 
  22.  
  23. else { 
  24.  
  25. fromAcct.debit(amount); 
  26.  
  27. toAcct.credit(amount); 
  28.  
  29.  
  30.  
  31.  
  32. // 得到两个锁的hash值 
  33.  
  34. int fromHash = System.identityHashCode(fromAcct); 
  35.  
  36. int toHash = System.identityHashCode(toAcct); 
  37.  
  38. // 根据hash值判断锁顺序,决定锁的顺序 
  39.  
  40. if (fromHash < toHash) { 
  41.  
  42. synchronized (fromAcct) { 
  43.  
  44. synchronized (toAcct) { 
  45.  
  46. new Helper().transfer(); 
  47.  
  48.  
  49.  
  50. else if (fromHash > toHash) { 
  51.  
  52. synchronized (toAcct) { 
  53.  
  54. synchronized (fromAcct) { 
  55.  
  56. new Helper().transfer(); 
  57.  
  58.  
  59.  
  60. else {// 如果两个对象的hash相等,通过tieLock来决定加锁的顺序,否则又会重新引入死锁——加时赛锁 
  61.  
  62. synchronized (tieLock) { 
  63.  
  64. synchronized (fromAcct) { 
  65.  
  66. synchronized (toAcct) { 
  67.  
  68. new Helper().transfer(); 
  69.  
  70.  
  71.  
  72.  
  73.  
  74.  

4.3 支持定时的锁(超时放弃)

有一项技术可以检测死锁和从死锁中恢复过来,就是使用Lock类中的定时 publicbooleantryLock(longtime,TimeUnitunit)throwsInterruptedException功能,来代替内置锁机制,当使用内置锁的时候,只要没有获得锁,就会永远等待下去,而 tryLock可以指定一个超时时间 (Timeout),在等待超过时间后 tryLock会返回一个失败信息,如果超时时限比获取锁的时间要长很多,那么就可以在发生某个意外后重新获得控制权。如下图所示: 

4.4 死锁避免

死锁防止方法能够防止发生死锁,但必然会降低系统并发性,导致低效的资源利用率,其中最具有代表性的避免死锁算法是银行家算法。

1、多个资源的银行家算法


上图中有五个进程,四个资源。左边的图表示已经分配的资源,右边的图表示还需要分配的资源。最右边的 E、P 以及 A 分别表示:总资源、已分配资源以及可用资源,注意这三个为向量,而不是具体数值,例如 A=(1020),表示 4 个资源分别还剩下 1/0/2/0。

检查一个状态是否安全的算法如下:

4.5 死锁检测

Jconsole是JDK自带的监控工具,在JDK/bin目录下可以找到。它用于连接正在运行的本地或者

远程的JVM,对运行在Java应用程序的资源消耗和性能进行监控,并画出大量的图表,提供强大

的可视化界面。而且本身占用的服务器内存很小,甚至可以说几乎不消耗。

4.5 死锁恢复

撤销陷入死锁的所有进程,解除死锁,继续运行。

逐个撤销陷入死锁的进程,回收其资源并重新分配,直至死锁解除。 可选择符合下面条件之一的先撤销:

  1. CPU消耗时间最少者
  2. 产生的输出量最小者
  3. 预计剩余执行时间最长者
  4. 分得的资源数量最少者后优先级最低者

系统重启:结束所有进程的执行并重新启动操作系统。这种方法很简单,但先前的工作全部作废。

 

来源:牧小农内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯