📫作者简介:小明Java问道之路,2022年度博客之星全国TOP3,专注于后端、中间件、计算机底层、架构设计演进与稳定性建设优化,文章内容兼具广度、深度、大厂技术方案,对待技术喜欢推理加验证,就职于知名金融公司后端高级工程师。
🏆 2022博客之星TOP3 | CSDN博客专家 | 后端领域优质创作者 | CSDN内容合伙人
🏆 InfoQ(极客邦)签约作者、阿里云专家 | 签约博主、51CTO专家 | TOP红人、华为云享专家
🔥如果此文还不错的话,还请👍关注、点赞、收藏三连支持👍一下博主~
🍅 文末获取联系 🍅 👇🏻 精彩专栏推荐订阅收藏 👇🏻
专栏系列(点击解锁)
学习路线(点击解锁)
知识定位
🔥Redis从入门到精通与实战🔥
Redis从入门到精通与实战
围绕原理源码讲解Redis面试知识点与实战
🔥MySQL从入门到精通🔥
MySQL从入门到精通
全面讲解MySQL知识与企业级MySQL实战 🔥计算机底层原理🔥
深入理解计算机系统CSAPP
以深入理解计算机系统为基石,构件计算机体系和计算机思维
Linux内核源码解析
围绕Linux内核讲解计算机底层原理与并发
🔥数据结构与企业题库精讲🔥
数据结构与企业题库精讲
结合工作经验深入浅出,适合各层次,笔试面试算法题精讲
🔥互联网架构分析与实战🔥
企业系统架构分析实践与落地
行业最前沿视角,专注于技术架构升级路线、架构实践
互联网企业防资损实践
互联网金融公司的防资损方法论、代码与实践
🔥Java全栈白宝书🔥
精通Java8与函数式编程
本专栏以实战为基础,逐步深入Java8以及未来的编程模式
深入理解JVM
详细介绍内存区域、字节码、方法底层,类加载和GC等知识
深入理解高并发编程
深入Liunx内核、汇编、C++全方位理解并发编程
Spring源码分析
Spring核心七IOC/AOP等源码分析
MyBatis源码分析
MyBatis核心源码分析
Java核心技术
只讲Java核心技术
本文目录
本文导读
一、死锁是什么?
二、死锁的 4 个必要条件
三、死锁的修复策略
1、避免策略
2、检测与恢复策略
四、线上发生死锁应该怎么办
五、如何用命令行和代码定位死锁?
1、jstack命令
2、ThreadMXBean
总结
本文导读
本文讲解死锁是什么,死锁产生的4个条件以及如何避免死锁,并根据实际线上发生死锁应该怎么办,如何用命令行和代码定位死锁进行讲解。
一、死锁是什么?
死锁是指两个或多个线程(进程)在同一资源上相互占用,并请求锁定对方的资源,从而导致恶性循环的现象。
二、死锁的 4 个必要条件
互斥条件:每个资源每次只能被一个线程(或进程,下同)使用,如果每个人都可以拿到想要的资源,那就不需要等待,所以是不可能发生死锁的。
请求与保持条件:当一个线程试图获取资源,但发生了阻塞,则需对已获得的资源保持不放。如果在请求资源时阻塞了,并且会自动释放手中资源(例如锁)的话,那别人自然就能拿到我刚才释放的资源,也就不会形成死锁。
不剥夺条件:它是指线程已获得的资源,在未使用完之前,不会被强行剥夺。
循环等待条件:通俗得讲就是多个线程之间必须形成“循环等待”,才有可能形成死锁,比如在两个线程之间,这种“循环等待”就意味着它们互相持有对方所需的资源、互相等待。
三、死锁的修复策略
1、避免策略
避免策略最主要的思路就是,优化代码逻辑,从根本上消除发生死锁的可能性。通常而言,发生死锁的一个主要原因是顺序相反的去获取不同的锁。因此大家获取锁的顺序就一样了,就不会出现获取锁顺序相反的情况
synchronized (from) {
synchronized (to) {
}
}
2、检测与恢复策略
一旦发生死锁,就可以用死锁恢复机制(用命令行和代码定位死锁),比如剥夺某一个资源,来解开死锁,进行恢复;线程终止;资源抢占(让线程回退几步、 释放资源)。
四、线上发生死锁应该怎么办
如果线上发生死锁问题,最好的办法是保存 JVM 信息、日志等“案发现场”的数据,然后立刻重启服务,来尝试修复死锁。
为什么说重启服务能解决问题?因为发生死锁往往要有很多前提条件的,并且当并发度足够高的时候才有可能会发生死锁,所以重启后再次立刻发生死锁的几率并不是很大,当我们重启服务器之后,就可以暂时保证线上服务的可用,然后利用刚才保存过的案发现场的信息,排查死锁、修改代码,最终重新发布。
五、如何用命令行和代码定位死锁?
1、jstack命令
jstack pid是查看Java 线程的一些相关信息,如果是比较明显的死锁关系,那么这个工具就可以直接检测出来;
如果死锁不明显,那么它无法直接检测出来,不过我们也可以借此来分析线程状态,进而就可以发现锁的相互依赖关系,所以这也是很有利于我们找到死锁的方式。
jstack 可以找到死锁,把哪个线程、想要获取哪个锁、形成什么样的环路展示出来,当我们有了这样的信息之后,死锁就非常容易定位了,所以接下来我们就可以进一步修改代码,来避免死锁了。
打印:
Found one Java-level deadlock:
=============================
"t2": waiting to lock monitor 0x00007fa06c004a18 (object 0x000000076adabaf0, a java.lang.Object),
which is held by "t1"
"t1": waiting to lock monitor 0x00007fa06c007358 (object 0x000000076adabb00, a java.lang.Object),
which is held by "t2"
Java stack information for the threads listed above:
===================================================
"t2": at lesson67.MustDeadLock.run(MustDeadLock.java:31)
- waiting to lock <0x000000076adabaf0> (a java.lang.Object)
- locked <0x000000076adabb00> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
"t1": at lesson67.MustDeadLock.run(MustDeadLock.java:19)
- waiting to lock <0x000000076adabb00> (a java.lang.Object)
- locked <0x000000076adabaf0> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock
2、ThreadMXBean
使用 ThreadMXBean 可以检测程序中出现死锁的线程,获取该线程的相关信息。相比较于jstack 在命令中查看,此种写代码的方式,能够在线程出现死锁的时候,做一些对应的操作
通过 ThreadMXBean 的 findDeadlockedThreads 方法,可以获取到一个 deadlockedThreads 的数组,然后进行判断,当这个数组不为空且长度大于 0 的时候,我们逐个打印出对应的线程信息
比如我们打印出了线程 id,也打印出了线程名,同时打印出了它所需要的那把锁正被哪个线程所持有,在业务代码中加入这样的检测,那我们就可以在发生死锁的时候及时地定位,同时进行报警等其他处理。
public static void main(String[] args) throws InterruptedException {
ThreadMXBeanDetection r1 = new ThreadMXBeanDetection();
ThreadMXBeanDetection r2 = new ThreadMXBeanDetection();
//给不同的线程, 设置不同的标记位
r1.flag=1;
r2.flag=2;
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
//让两个子线程执行
Thread.sleep(1000);
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
//获取发生死锁的线程id数组
long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
//判断发生死锁的线程是否为空
if (deadlockedThreads != null && deadlockedThreads.length > 0) {
for (int i = 0; i < deadlockedThreads.length; i++) {
//传入发生死锁的线程id , 获取发生死锁的线程信息
ThreadInfo threadInfo = threadMXBean.getThreadInfo(deadlockedThreads[i]);
//打印出发生死锁线程的名称
System.out.println("线程id为"+threadInfo.getThreadId()+",死锁线程名为" + threadInfo.getThreadName()+"的线程已经发生死锁,需要的锁正被线程"+threadInfo.getLockOwnerName()+"持有。");
}
}
}
总结
本文讲解死锁是什么,死锁产生的4个条件以及如何避免死锁,并根据实际线上发生死锁应该怎么办,如何用命令行和代码定位死锁进行讲解。