当前位置: 首页 > news >正文

java多线程7 死锁_Java多线程7:死锁

前言

死锁单独写一篇文章是因为这是一个很严重的、必须要引起重视的问题。这不是夸大死锁的风险,尽管锁被持有的时间通常很短,但是作为商业产品的应用程序每天可能要执行数十亿次获取锁->释放锁的操作,只要在这数十亿次操作中只要有一次发生了错误,就可能导致程序中发生死锁,并且即使通过压力测试也不可能找出所有潜在的死锁。

死锁

一个经典的多线程问题。

当一个线程永远地持有一个锁,并且其他线程都尝试去获得这个锁时,那么它们将永远被阻塞,这个我们都知道。如果线程A持有锁L并且想获得锁M,线程B持有锁M并且想获得锁L,那么这两个线程将永远等待下去,这种情况就是最简单的死锁形式。

在数据库系统的设计中考虑了监测死锁以及从死锁中恢复,数据库如果监测到了一组事务发生了死锁时,将选择一个牺牲者并放弃这个事务。Java虚拟机解决死锁问题方面并没有数据库这么强大,当一组Java线程发生死锁时,这两个线程就永远不能再使用了,并且由于两个线程分别持有了两个锁,那么这两段同步代码/代码块也无法再运行了----除非终止并重启应用。

死锁是设计的BUG,问题比较隐晦。不过死锁造成的影响很少会立即显现出来,一个类可能发生死锁,并不意味着每次都会发生死锁,这只是表示有可能。当死锁出现时,往往是在最糟糕的情况----高负载的情况下。

下面给出一个产生死锁的简单代码并且演示如何分析这是一个死锁:

public classDeadLock

{private final Object left = newObject();private final Object right = newObject();public void leftRight() throwsException

{synchronized(left)

{

Thread.sleep(2000);synchronized(right)

{

System.out.println("leftRight end!");

}

}

}public void rightLeft() throwsException

{synchronized(right)

{

Thread.sleep(2000);synchronized(left)

{

System.out.println("rightLeft end!");

}

}

}

}

注意这里一定要有"Thread.sleep(2000)"让线程睡一觉,不然一个线程运行了,另一个线程还没有运行,先运行的线程很有可能就已经连续获得两个锁了。写两个线程分别调用它们:

public class Thread0 extendsThread

{privateDeadLock dl;publicThread0(DeadLock dl)

{this.dl =dl;

}public voidrun()

{try{

dl.leftRight();

}catch(Exception e)

{

e.printStackTrace();

}

}

}

public class Thread1 extendsThread

{privateDeadLock dl;publicThread1(DeadLock dl)

{this.dl =dl;

}public voidrun()

{try{

dl.rightLeft();

}catch(Exception e)

{

e.printStackTrace();

}

}

}

写个main函数调用一下:

public static voidmain(String[] args)

{

DeadLock dl= newDeadLock();

Thread0 t0= newThread0(dl);

Thread1 t1= newThread1(dl);

t0.start();

t1.start();while(true);

}

至于结果,没有结果,什么语句都不会打印,因为死锁了。下面演示一下如何定位死锁问题:

1、jps获得当前Java虚拟机进程的pid

a82db7735a73ba7bcec754b0a87bb8fb.png

2、jstack打印堆栈。jstack打印内容的最后其实已经报告发现了一个死锁,但因为我们是分析死锁产生的原因,而不是直接得到这里有一个死锁的结论,所以别管它,就看前面的部分

e3d903a0e1acbc88f51539833d296ff5.png

先说明介绍一下每一部分的意思,以"Thread-1"为例:

(1)"Thread-1"表示线程名称

(2)"prio=6"表示线程优先级

(3)"tid=00000000497cec00"表示线程Id

(4)nid=0x219c

线程对应的本地线程Id,这个重点说明下。因为Java线程是依附于Java虚拟机中的本地线程来运行的,实际上是本地线程在执行Java线程代码,只有本地线程才是真正的线程实体。Java代码中创建一个thread,虚拟机在运行期就会创建一个对应的本地线程,而这个本地线程才是真正的线程实体。Linux环境下可以使用"top -H -p JVM进程Id"来查看JVM进程下的本地线程(也被称作LWP)信息,注意这个本地线程是用十进制表示的,nid是用16进制表示的,转换一下就好了,0x219c对应的本地线程Id应该是8604。

(5)"[0x000000004a3bf000..0x000000004a3bf790]"表示线程占用的内存地址

(6)"java.lang.Thread.State:BLOCKED"表示线程的状态

解释完了每一部分的意思,看下Thread-1处于BLOCKED状态,Thread-0处于BLOCKED状态。对这两个线程分析一下:

(1)Thread-1获得了锁0x000000003416a4e8,在等待锁0x000000003416a4d8

(2)Thread-0获得了锁0x000000003416a4d8,在等待锁0x000000003416a4e8

由于两个线程都在等待获取对方持有的锁,所以就这么永久等待下去了。

3、注意一下使用Eclipse/MyEclipse,这段程序如果不点击控制台上面的红色方框去Terminate掉它,而是右键->Run As->1 Java Application的话,这个进程会一直存在的,这时候可以利用taskkill命令去终止没有被Terminate的进程:

cf5310dc49a035e5ddc4c2a820c8de5e.png

避免死锁的方式

既然可能产生死锁,那么接下来,讲一下如何避免死锁。

1、让程序每次至多只能获得一个锁。当然,在多线程环境下,这种情况通常并不现实

2、设计时考虑清楚锁的顺序,尽量减少嵌在的加锁交互数量

3、既然死锁的产生是两个线程无限等待对方持有的锁,那么只要等待时间有个上限不就好了。当然synchronized不具备这个功能,但是我们可以使用Lock类中的tryLock方法去尝试获取锁,这个方法可以指定一个超时时限,在等待超过该时限之后变回返回一个失败信息

相关文章:

  • chipseeker Y叔
  • java运行中出错null_java.io.IOException:运行exec()时出错工作目录:null环境:null...
  • poj2965 java_POJ 刷题顺序
  • 01-ChIPseq从入门到放弃
  • 02-chip-seq
  • JAVA对返回的xml文件解析_java解析微信返回xml数据流报错
  • js比java的优点_node.js 与java的优缺点比较
  • 批量修改文件夹子 修改文件名
  • Java波动型数据预测_性能数据波动问题
  • java重构php_用于PHP代码重构的工具
  • java运行环境配置心得_java环境变量心得
  • chip-seq三个生物学重复样品处理——IDR
  • java绘制图形和文本一_java绘制图形和文本
  • CHIP-SEQ 芯片分析时,对于来自重复实验的数据,怎样进行MACS peaks calling 分析?
  • java end()用法_int regionEnd()
  • 【跃迁之路】【733天】程序员高效学习方法论探索系列(实验阶段490-2019.2.23)...
  • CSS实用技巧
  • hadoop入门学习教程--DKHadoop完整安装步骤
  • in typeof instanceof ===这些运算符有什么作用
  • JavaScript HTML DOM
  • Vue UI框架库开发介绍
  • Yeoman_Bower_Grunt
  • 成为一名优秀的Developer的书单
  • 码农张的Bug人生 - 初来乍到
  • 容器服务kubernetes弹性伸缩高级用法
  • 移动端解决方案学习记录
  • 远离DoS攻击 Windows Server 2016发布DNS政策
  • #我与Java虚拟机的故事#连载16:打开Java世界大门的钥匙
  • (1)常见O(n^2)排序算法解析
  • (ctrl.obj) : error LNK2038: 检测到“RuntimeLibrary”的不匹配项: 值“MDd_DynamicDebug”不匹配值“
  • (Demo分享)利用原生JavaScript-随机数-实现做一个烟花案例
  • (Python) SOAP Web Service (HTTP POST)
  • (编译到47%失败)to be deleted
  • (十)T检验-第一部分
  • (十六)一篇文章学会Java的常用API
  • (十一)JAVA springboot ssm b2b2c多用户商城系统源码:服务网关Zuul高级篇
  • (轉貼) VS2005 快捷键 (初級) (.NET) (Visual Studio)
  • *_zh_CN.properties 国际化资源文件 struts 防乱码等
  • .java 9 找不到符号_java找不到符号
  • .net core webapi Startup 注入ConfigurePrimaryHttpMessageHandler
  • .NET Core工程编译事件$(TargetDir)变量为空引发的思考
  • .NET Framework 3.5中序列化成JSON数据及JSON数据的反序列化,以及jQuery的调用JSON
  • .netcore 6.0/7.0项目迁移至.netcore 8.0 注意事项
  • .NET大文件上传知识整理
  • .NET教程 - 字符串 编码 正则表达式(String Encoding Regular Express)
  • .NET命令行(CLI)常用命令
  • @manytomany 保存后数据被删除_[Windows] 数据恢复软件RStudio v8.14.179675 便携特别版...
  • [ 英语 ] 马斯克抱水槽“入主”推特总部中那句 Let that sink in 到底是什么梗?
  • [《百万宝贝》观后]To be or not to be?
  • [Angular 基础] - 数据绑定(databinding)
  • [C# 开发技巧]实现属于自己的截图工具
  • [I2C]I2C通信协议详解(一) --- 什么是I2C
  • [LeetCode][LCR190]加密运算——全加器的实现
  • [luogu4162 SCOI2009] 最长距离(最短路)
  • [luoguP1666] 前缀单词(DP)