更多博客请见 我的语雀知识库
一、 准备测试应用
- 新建一个测试程序,写一段线程死锁的代码:
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排查问题
- 启动 Arthas:
java -jar arthas-boot.jar
- 查看总体使用情况:
dashboard
可以看到已经有死锁线程了 - 查看总体线程使用情况:
thread
BLOCKED线程数量:2,并且显示了具体的BLOCKED线程 - 定位死锁的位置:
thread -b
输出解释:
-
线程信息:
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@1d0be4d0
。Thread 2
lock了java.util.ArrayLIst@1f2efe1c
-
相互阻塞:最关键的信息是
ownd by'thread 1
,指出Thread 2
尝试获取的锁被Thread 1
持有,同时说明Thread 1
至少在某一点上也尝试获取Thread 2
持有的锁(或者导致了其他形式的循环等待),从而形成了死锁。这里的but blocks 1 other threads!
暗示了这种相互阻塞的关系。
到目前为止。基本已经定位到死锁的具体原因和位置了。
- 查看死锁线程详细信息:
thread 21
thread 22
根据上面的信息,我们可以清晰地看到两个线程Thread 1
和Thread 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
的锁。 - 两个线程互相等待对方释放锁,形成了死锁。
解决这个问题的关键在于打破死锁的四大条件之一。即:
- 资源不可重用
- 循环等待
- 资源不可被抢夺
- 吃着碗里的看着锅里的