Java中死锁的定位与修复
死锁应该可以说是并发编程中比较常见的一种情况,可以说如果程序产生了死锁那将会对程序带来致命的影响;所以排查定位、修复死锁至关重要;
我们都知道死锁是由于多个对象或多个线程之间相互需要 对方锁持有的锁而又没有释放对方所持有的锁,导致双方都永久处于阻塞状态 ;
如上图所示,线程1持有对象1的锁、线程2持有对象2的锁,持此线程1又想去获取对象2对象锁、线程2想获取对象1对象锁,此时由于双方都没有获取到想要的锁,任务没完成所以也没释放锁,导致一直僵持呢,于是阻塞、产生死锁;
死锁检测
需要检测死锁肯定要先有死锁出现,下面的demo模拟了一个死锁的产生;
public class DeadlockDemo extends Thread { private BaseObj first; private BaseObj second; public DeadlockDemo(String name, BaseObj first, BaseObj second) { super(name); this.first = first; this.second = second; } public void reentrantLock() throws InterruptedException { first.lock(); System.out.println(String.format("%s 持有:%s 对象锁,等待获取:%s对象锁", this.getName(), first, second)); second.lock(); first.unlock(); second.unlock(); } @Override public void run() { try { reentrantLock(); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) throws InterruptedException { ObjOne one = new ObjOne(); ObjTwo two = new ObjTwo(); DeadlockDemo thread1 = new DeadlockDemo("Thread1", one, two); DeadlockDemo thread2 = new DeadlockDemo("Thread2", two, one); thread1.start(); thread2.start(); thread1.join(); thread2.join(); } }
class DeadlockDemo extends Thread { private BaseObj first; private BaseObj second; public DeadlockDemo(String name, BaseObj first, BaseObj second) { super(name); this.first = first; this.second = second; } public void reentrantLock() throws InterruptedException { first.lock(); System.out.println(String.format("%s 持有:%s 对象锁,等待获取:%s对象锁", this.getName(), first, second)); second.lock(); first.unlock(); second.unlock(); } @Override public void run() { try { reentrantLock(); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) throws InterruptedException { ObjOne one = new ObjOne(); ObjTwo two = new ObjTwo(); DeadlockDemo thread1 = new DeadlockDemo("Thread1", one, two); DeadlockDemo thread2 = new DeadlockDemo("Thread2", two, one); thread1.start(); thread2.start(); thread1.join(); thread2.join(); } }
运行上面的demo将看到程序被阻塞了,没法结束运行;只看到如下运行结果:
Thread1 持有:objOne 对象锁,等待获取:objTwo对象锁 Thread2 持有:objTwo 对象锁,等待获取:objOne对象锁
这demo没法结束运行就是由于产生了死锁,两个线程都在相互对待获取对方所持有的对象锁;
这时候要解决问题就需要找出哪里出现了死锁, 通过代码走查通常不容易发现死锁 ,当然我们这程序很容易发现,因为我们刻意产生的死锁;所以就需要工具来检测死锁,这里可用的工具主要有:jconsole、jvisualvm、jstack等,这些工具其实都是jdk自带的,用法都很类似;
这里使用jvisualvm来检测当前的demo程序是否产生了死锁;打开jvisualvm连接到当前的应用程序即可看到程序的监控信息,如内存、CPU、性能、GC等等;打开进入线程的tab项查看程序的线程信息,这里很明显的就看到了提示该程序被检测除了死锁!
点击 线程Dump可以看到线程的堆栈信息,从中可以看到线程的详细信息,并定位死锁;
从上图可以看到线程产生死锁的原因,Thrad2是等待Thread1、Thread1是等待Thread1, 从下图的堆栈信息即可定位死锁产生的位置;
死锁扫描
除了发现程序出现问题后我们去扫描死锁外,我们还可以实时的去扫描程序用于发现程序中是否存在死锁;
JDK提供了MXBean Api可用于扫描程序是否存在死锁,ThreadMXBean提供了findDeadlockedThreads()方法,可以用于找到产生死锁的线程;这里在上面的demo程序中添加一个方法用于扫描死锁,虽然这种方法可以扫描到死锁但是由于每次都对线程打快照对程序性能会有比较大的影响,所以慎用;
public static void scanDeadLock() { ThreadMXBean mxBean = ManagementFactory.getThreadMXBean(); Runnable runnable = () -> { long[] ids = mxBean.findDeadlockedThreads(); System.out.println("扫描死锁..."); if (ids != null) { ThreadInfo[] threadInfos = mxBean.getThreadInfo(ids); for (ThreadInfo threadInfo : threadInfos) { System.out.println(threadInfo); } } }; ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(5, Executors.defaultThreadFactory()); executorService.scheduleAtFixedRate(runnable, 1, 5, TimeUnit.SECONDS); }
static void scanDeadLock() { ThreadMXBean mxBean = ManagementFactory.getThreadMXBean(); Runnable runnable = () -> { long[] ids = mxBean.findDeadlockedThreads(); System.out.println("扫描死锁..."); if (ids != null) { ThreadInfo[] threadInfos = mxBean.getThreadInfo(ids); for (ThreadInfo threadInfo : threadInfos) { System.out.println(threadInfo); } } }; ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(5, Executors.defaultThreadFactory()); executorService.scheduleAtFixedRate(runnable, 1, 5, TimeUnit.SECONDS); }
避免死锁
解决死锁最好的方法就是避免死锁了,比如上面的demo我们可以把直接使用无参数的lock()方法换为使用tryLock方法,tryLock还可以指定获取锁超时时间,到了超时时间还没获得到锁就会放弃获取锁,当然还有其它方法可以避免死锁;
1、避免使用多个锁、长时间持有锁;
2、设计好多个锁的获取顺序
3、使用带超时的获取锁方法
如果对自己未来有想法,想提升自己,你现在在JAVA这条路上挣扎,也想在IT行业拿高薪,可以参加我们免费的公开课试听学习 干货满满的,选择最适合自己的课程学习,技术大牛亲授,课程内容有:Java工程化、高性能及分布式、高性能、深入浅出。高架构。性能调优、Spring,MyBatis,Netty源码分析和大数据等多个知识点。如果你想拿高薪的,想学习的,想就业前景好的,想跟别人竞争能取得优势的,想进阿里面试但担心面试不过的,你都可以来。
群号:468947140
进群修改群备注:开发年限-地区-经验
点击链接加入群聊【Java-BATJ企业级资深架构】:https://jq.qq.com/?_wv=1027&k=52j2FVO
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
C/C++中int128的那点事
最近群友对int128这个东西讨论的热火朝天的。讲道理的话,编译器的gcc是不支持__int128这种数据类型的,比如在codeblocks 16.01/Dev C++是无法编译的,但是提交到大部分OJ上是可以编译且能用的。C/C++标准。IO是不认识__int128这种数据类型的,因此要自己实现IO,其他的运算,与int没有什么不同。 但是官方上写了GCC提供了两种128位整数类型,分别是__int128_t和__uint128_t,分别用于声明有符号整数变量和无符号整数变量。 有关GCC的文档参见:Using the GNU Compiler Collection (GCC)。 这里给出了样例程序,是有关类型__int128_t和__uint128_t的。从计算可以看出,这两个类型都是16字节的,类型__uint128_t是128位的。程序中使用了按位取反运算,移位运算和乘法运算。 由于这种大整数无法使用函数printf()输出其值,所以自己做了一个整数转字符串函数myitoa(),用于实现128位整数的输出。 有兴趣的同学想了解底层实现原理可以参看我的Github上:https:...
- 下一篇
复盘微信支付金额不正确问题解决过程——PHP浮点型计算
问题 2017年9月份,商城项目在运行过程中,购买某商品时如果在下单时没有完成付款,而是稍后再从“个人中心-我的订单”发起付款,则无法调起微信支付界面 思路 其他商品正常,说明导致问题的原因大概率是商品本身 只有从会员中心发起的付款存在此问题,说明大概率是会员中心的代码存在问题 需要先观察问题出现时“统一下单”是否能够成功,检查是否是参数问题导致订单无法在微信端创建 观察统一下单返回值 result_code=FAIL err_code=OUT_TRADE_NO_USED err_code_des=商户订单号重复 微信官方对于此问题的描述如下: image.png 出现这个问题的时候建议核查订单号是否重复提交,但实际上在这个使用场景下,我们是“故意”重复提交订单号的。因为从会员中心发起支付的时候订单已经创建了,系统会再次请求微信统一下单接口,即便如此,我们也没有必要每一次请求支付都创建一个新的订单号。 那为什么返回了这个错误 我先给出结论再描述排错过程: 所谓的同一笔交易不能多次提交,实际上指的是在商品描述、标价金额不相同的情况下,用同一个订单号访问了统一下单接口。 image.png...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS7安装Docker,走上虚拟化容器引擎之路
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- Docker安装Oracle12C,快速搭建Oracle学习环境
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果