Java死锁检测以及解决办法
一、死锁概念
1. 什么是死锁?
两个或者多个线程互相持有对方所需要的资源, 都在等待对方执行完毕才能继续往下执行的时候,就称为发生了死锁。结果就是两个线程或多个线程都陷入了无限的等待中。由于线程被无限期地阻塞,因此程序不可能正常终止。
一般是有多个锁对象的情况下并且获得锁顺序不一致造成的。
2. java 死锁产生的四个必要条件
1) 互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
2)不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
3)请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
4)循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
3. 死锁的影响
1)系统资源浪费
一旦发生死锁,相关的进程会无限期地占用系统资源,而这些资源无法被其他进程使用,导致系统资源的浪费和利用率降低。
2)系统性能下降
死锁会导致进程长时间等待,影响系统响应时间,严重时甚至可能让系统无法响应新的请求,导致性能显著下降。
3)系统稳定性降低
死锁使得部分进程停滞,从而影响依赖这些进程的其他部分,甚至导致整个系统失去响应,影响系统的稳定性和可靠性。
4. 如何避免死锁?
为了避免死锁的危害,通常需要在设计阶段进行预防,包括资源分配有序化、锁定顺序控制、减少锁的粒度等策略,同时在运行时进行死锁检测和恢复。
二、死锁示例
代码示例如下:
1 public class DeadLock {
2 /**
3 * 创建两个对象,用两个线程分别先后独占
4 */
5 private Boolean flag1 = true;
6 private Boolean flag2 = false;
7
8 public static void main(String[] args) {
9 DeadLock deadLock = new DeadLock();
10
11 new Thread(new Runnable() {
12 @Override
13 public void run() {
14 System.out.println("线程1开始,作用是当flag1 = true 时,将flag2也改为 true");
15 synchronized (deadLock.flag1){
16 if(deadLock.flag1){
17 try{
18 //睡眠1s ,模拟业务执行耗时,并保证两个线程进入死锁状态
19 Thread.sleep(1000);
20 }catch (InterruptedException e){
21 e.printStackTrace();
22 }
23 System.out.println("flag1 = true,准备锁住flag2...");
24 synchronized (deadLock.flag2){
25 deadLock.flag2 = true;
26 }
27 }
28 }
29 }
30 }).start();
31
32 new Thread(new Runnable() {
33 @Override
34 public void run() {
35 System.out.println("线程2开始,作用是当flag2 = false 时,将flag1也改为 false");
36 synchronized (deadLock.flag2){
37 if(!deadLock.flag2){
38 try{
39 //睡眠1s ,模拟业务执行耗时,并保证两个线程进入死锁状态
40 Thread.sleep(1000);
41 }catch (InterruptedException e){
42 e.printStackTrace();
43 }
44 System.out.println("flag2 = false,准备锁住flag1...");
45 synchronized (deadLock.flag1){
46 deadLock.flag1 = false;
47 }
48
49 }
50 }
51 }
52 }).start();
53 }
54 }
三、死锁检测
1. jps
jps -l
查看所有的jvm进程,包括进程ID,进程启动的路径等等。
如下图:
2. jstack
jstack [pid]
jstack命令用于生成虚拟机当前时刻的线程快照。通过这个命令,可以观察jvm中当前所有线程的运行情况和线程当前状态。
执行命令:jstack 10928,截取部分运行结果,如下图:
3. 可视化工具 jconsole
从Java 5开始 引入了 JConsole,JConsole 是一个内置 Java 性能分析器。
主要用于基础的 JVM 监控,适合于查看应用程序的基本运行状况。它提供了一些基础指标,比如 CPU、内存使用率、线程状态、类加载信息以及基本的 MBeans 信息。它简单轻便,适合对应用进行基本的监控。
1)在命令终端输入:jconsole
2)点击连接进去
进入所检测的进程后,选择“线程”选项卡,并点击“检测死锁”。即可出来检测结果。如下图:
4. 可视化工具 visual VM
Visual VM 功能比 JConsole 更全面,除了 JConsole 提供的基础监控功能外,VisualVM 还支持更复杂的调试和性能分析。它可以进行 CPU 和内存的详细分析、线程分析、内存泄露检测,还可以生成堆转储文件、线程转储、GC 日志等,并支持通过插件扩展功能,比如集成 BTrace 进行动态追踪。
可以点击右上角Dump按钮,将线程的信息导出,其实就是执行的jstack命令