文章目录
- 死锁的概念
- 死锁定位
- 实战
- 模拟问题定位
- 使用 jstack
- 使用JConsole
- 使用 Java 提供的标准管理 API
死锁的概念
- 死锁是一种特定的程序状态,在实体之间,由于循环依赖导致彼此一直处于等待之中,没有任何个体可以继续前进。死锁不仅仅是在线程之间会发生,存在资源独占的进程之间同样也可能出现死锁。通常来说,我们大多是聚焦在多线程场景中的死锁,指两个或多个线程之间,由于互相持有对方需要的锁,而永久处于阻塞的状态。
死锁定位
- 定位死锁最常见的方式就是利用 jstack 等工具获取线程栈,然后定位互相之间的依赖关系,进而找到死锁。如果是比较明显的死锁,往往 jstack 等就能直接定位,类似 JConsole 甚至可以在图形界面进行有限的
死锁检测
。 - 如果程序运行时发生了死锁,绝大多数情况下都是无法在线解决的,只能重启、修正程序本身问题。所以,代码开发阶段互相审查,或者利用工具进行预防性排查,往往也是很重要的。
实战
一个基本的死锁程序例子
public class DeadLockSample extends Thread {
private String first;
private String second;
public DeadLockSample(String name, String first, String second) {
super(name);
this.first = first;
this.second = second;
}
public void run() {
synchronized (first) {
System.out.println(this.getName() + " obtained: " + first);
try {
Thread.sleep(1000L);
synchronized (second) {
System.out.println(this.getName() + " obtained: " + second);
}
} catch (InterruptedException e) {
// Do nothing
}
}
}
public static void main(String[] args) throws InterruptedException {
// 死锁样例代码
String lockA = "lockA";
String lockB = "lockB";
DeadLockSample t1 = new DeadLockSample("Thread1", lockA, lockB);
DeadLockSample t2 = new DeadLockSample("Thread2", lockB, lockA);
t1.start();
t2.start();
t1.join();
t2.join();
}
}
- 程序编译执行后,几乎每次都可以重现死锁
模拟问题定位
使用 jstack
- 首先使用 jps 或者系统的 ps 命令、任务管理器等工具,确定进程 ID
jps
- 然后,调用 jstack 获取线程栈(jstack在jdk包的bin目录下)
jstack 24468
"Thread2" #21 prio=5 os_prio=0 tid=0x000000001dfa3000 nid=0x3ab0 waiting for monitor entry [0x0000000020a6f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.carroll.test.thread.DeadLockSample.run(DeadLockSample.java:25) //具体的类以及代码行数
- waiting to lock <0x0000000776258c30> (a java.lang.String) // 等待获取锁 <0x0000000776258c30>
- locked <0x0000000776258c68> (a java.lang.String) // 已经持有锁 <0x0000000776258c68>
"Thread1" #20 prio=5 os_prio=0 tid=0x000000001dfa2000 nid=0x46f4 waiting for monitor entry [0x000000002096e000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.carroll.test.thread.DeadLockSample.run(DeadLockSample.java:25) //具体的类以及代码行数
- waiting to lock <0x0000000776258c68> (a java.lang.String) // 等待获取锁 <0x0000000776258c68>
- locked <0x0000000776258c30> (a java.lang.String) // 已经持有锁 <0x0000000776258c30>
- 上面这个输出非常明显,找到处于 BLOCKED 状态的线程,按照试图获取(waiting)的锁 ID查找,很快就定位问题。 jstack 本身也会把类似的简单死锁抽取出来,直接打印出来。
实际应用中,类死锁情况未必有如此清晰的输出,但是总体上可以理解为:区分线程状态 -> 查看等待目标 -> 对比 Monitor 等持有状态
使用JConsole
- 找到jdk包的bin目录,双击打开
jconsole
使用 Java 提供的标准管理 API
-
ThreadMXBean:Java 虚拟机线程系统的管理接口。
- Java 虚拟机具有此接口的实现类的单一实例。实现此接口的实例是一个 MXBean,可以通过调用
ManagementFactory.getThreadMXBean()
方法或从平台 MBeanServer 方法获得它。
- Java 虚拟机具有此接口的实现类的单一实例。实现此接口的实例是一个 MXBean,可以通过调用
-
在上面main方法中加入下面的代码片段:
ThreadMXBean mbean = ManagementFactory.getThreadMXBean();
Runnable dlCheck = () -> {
long[] threadIds = mbean.findDeadlockedThreads();
if (threadIds != null) {
ThreadInfo[] threadInfos = mbean.getThreadInfo(threadIds);
System.out.println("Detected deadlock threads:");
for (ThreadInfo threadInfo : threadInfos) {
System.out.println(threadInfo.getThreadName());
}
}
};
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
// 稍等 5 秒,然后每 10 秒进行一次死锁扫描
scheduler.scheduleAtFixedRate(dlCheck, 5L, 10L, TimeUnit.SECONDS);
你知道的越多,你不知道的越多。