一、 准备测试应用

  1. 新建一个测试程序,写一段线程死锁的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import java.util.*;

public class Test {
static List<Integer> resource1 = new ArrayList<>();
static List<Integer> resource2 = new ArrayList<>();
public static void main(String[] args) {
threadLock();
}
public static void threadLock() {
Thread thread1 = new Thread(() -> {
synchronized (resource1) {
System.out.println(Thread.currentThread().getName() + " got resource1 lock.");
try {
Thread.sleep(100); // 模拟工作时间,让死锁更容易观察到
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " trying to get resource2 lock.");
synchronized (resource2) { // 尝试获取resource2的锁
System.out.println(Thread.currentThread().getName() + " got resource2 lock.");
}
}
}, "Thread 1");

Thread thread2 = new Thread(() -> {
synchronized (resource2) {
System.out.println(Thread.currentThread().getName() + " got resource2 lock.");
try {
Thread.sleep(100); // 模拟工作时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " trying to get resource1 lock.");
synchronized (resource1) { // 尝试获取resource1的锁
System.out.println(Thread.currentThread().getName() + " got resource1 lock.");
}
}
}, "Thread 2");

thread1.start();
thread2.start();
}
}

二、使用Arthas排查问题

  1. 启动 Arthas:
    java -jar arthas-boot.jar

image.png
image.png

  1. 查看总体使用情况:
    dashboardimage.png
    可以看到已经有死锁线程了
  2. 查看总体线程使用情况:
    threadimage.pngBLOCKED线程数量:2,并且显示了具体的BLOCKED线程
  3. 定位死锁的位置:
    thread -bimage.png

输出解释:

  • 线程信息Thread 2 的ID为22,当前状态为BLOCKED。这意味着它正在等待获取某个对象锁。
  • 阻塞原因Thread 2被阻塞是因为需要获取的对象java.util.ArrayLIst@1d0be4d0正被另一个线程所持有。
  • 锁持有者:该对象锁java.util.ArrayLIst@1d0be4d0目前被Thread 1(ID为21)持有。
  • Thread 1的状态:虽然输出信息没有直接展示Thread 1的状态,但从上下文可以推断,Thread 1 locak了java.util.ArrayLIst@1d0be4d0Thread 2 lock了java.util.ArrayLIst@1f2efe1c
  • 相互阻塞:最关键的信息是ownd by'thread 1 ,指出Thread 2尝试获取的锁被Thread 1持有,同时说明Thread 1至少在某一点上也尝试获取Thread 2持有的锁(或者导致了其他形式的循环等待),从而形成了死锁。这里的but blocks 1 other threads!暗示了这种相互阻塞的关系。

到目前为止。基本已经定位到死锁的具体原因和位置了。

  1. 查看死锁线程详细信息:
    thread 21
    thread 22image.png

根据上面的信息,我们可以清晰地看到两个线程Thread 1Thread 2都处于阻塞状态,形成了死锁:

  • Thread 1 (Id=21) 的状态也是BLOCKED,它在Test.java:38行尝试获取对象java.util.ArrayLIst@1f2efe1c的锁,而这个锁正被Thread 2持有。
  • Thread 2 (Id=22) 的状态是BLOCKED,它在Test.java:23行尝试获取对象java.util.ArrayLIst@1d0be4d0的锁,但这个锁正被Thread 1持有。

这种相互等待对方释放锁的情形正是死锁的经典表现。每个线程都持有一个锁,并尝试获取对方的锁,导致双方都无法继续执行下去。

  • Thread 1在执行到Test.java的第38行时阻塞,等待java.util.ArrayLIst@1f2efe1c的锁。
  • Thread 2在执行到同文件的第23行时阻塞,等待java.util.ArrayLIst@1d0be4d0的锁。
  • 两个线程互相等待对方释放锁,形成了死锁。

解决这个问题的关键在于打破死锁的四大条件之一。即:

  1. 资源不可重用
  2. 循环等待
  3. 资源不可被抢夺
  4. 吃着碗里的看着锅里的