1)实现
package com.next1b.nextsvr.util.deadlockcheck;
import com.next1b.nextsvr.util.log.Log;
import java.lang.management.*;
import java.util.HashMap;
import java.util.Map;
/**
* 查找死锁,打印死锁上下文
*/
public final class DeadLockCheckThread extends Thread {
private final static int MAX_DEPTH = 255;
// 最大检测等待时间
private static final int MaxCheckPeriodMs = 5000;
// 当前线程是否还在运行
private volatile boolean running = true;
// jmx线程对象
private final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
// 多久检测一次死锁
private int sleepIdleMs = 1000;
public DeadLockCheckThread() {
super("DeadLockCheckThread");
// 守护线程
this.setDaemon(true);
}
@Override
public void run() {
while (running) {
try {
// 检测到了死锁
if (checkDeadLock()) { // 核心方法,检测死锁
sleepIdleMs = 1000; // 检测到了,则改为下次1s后再进行下次检测
} else {
sleepIdleMs += 1000; // 没检测到,则加大下次检测的时间,就不要那么频繁了
if (sleepIdleMs > MaxCheckPeriodMs) {
sleepIdleMs = MaxCheckPeriodMs;
}
}
// 休眠一会
sleepIdle(sleepIdleMs);
} catch (Throwable e) {
Log.logException(e);
}
}
}
/**
* 关闭
*/
public void shutdown() {
running = false;
}
/**
* 检测死锁
*/
private boolean checkDeadLock() {
// step1:返回死锁的线程Id列表
long[] deadlockedThreadIds = threadMXBean.findDeadlockedThreads();
if (null == deadlockedThreadIds) {
return false; // 说明没找到死锁
}
// step2:走到这里,说明找到了死锁
// 构建死锁线程信息映射 <threadId, ThreadInfo>
Map<Long, ThreadInfo> deadLockedThreadId2ThreadInfoMap = new HashMap<>();
ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(deadlockedThreadIds, threadMXBean.isObjectMonitorUsageSupported(), threadMXBean.isSynchronizerUsageSupported());
for (ThreadInfo threadInfo : threadInfos) {
try {
if (null != threadInfo
&& threadInfo.getLockOwnerId() != -1) { // getLockOwnerId == -1,不在等待被其他线程拥有的锁. 肯定不是环的一部分。
deadLockedThreadId2ThreadInfoMap.put(threadInfo.getThreadId(), threadInfo);
}
} catch (Exception e) {
Log.logException(e);
}
}
// step3:检测死锁环,打印下死锁信息
while (!deadLockedThreadId2ThreadInfoMap.isEmpty()) {
// 检查死锁
Map<Long, ThreadInfo> threadId2ThreadInfoCycleMap = new HashMap<>();
// 先拿到第一个线程信息
ThreadInfo threadInfo = deadLockedThreadId2ThreadInfoMap.entrySet().iterator().next().getValue();
do {
if (null != threadId2ThreadInfoCycleMap.put(threadInfo.getThreadId(), threadInfo)) { // 环找到了,说明有死锁了
// 打印发现的死锁信息
StringBuilder sb = new StringBuilder();
// 打印的时候把挂在环上的线程也打出来。
for (ThreadInfo info : threadId2ThreadInfoCycleMap.values()) {
fillThreadInfo(info, sb);
}
Log.logErr("\n----------DEAD LOCK BEGIN----------\n" + sb + "----------DEADLOCK END----------");
// 打断当前的do while循环,执行外层的while循环并尝试寻找下一个死锁.
break;
}
} while ((threadInfo = deadLockedThreadId2ThreadInfoMap.get(threadInfo.getLockOwnerId())) != null);
// 删除已经被处理的线程。cycle是完整的环,或者是那些等待的环已被打破剩下的孤立枝节。
deadLockedThreadId2ThreadInfoMap.keySet().removeAll(threadId2ThreadInfoCycleMap.keySet());
}
return true;
}
/**
* @param threadInfo 死锁环中线程的信息
* @param sb 线程信息
*/
private void fillThreadInfo(ThreadInfo threadInfo, StringBuilder sb) {
// 当前死锁的线程信息
sb.append("ThreadName=").append(threadInfo.getThreadName()).append(" ThreadId=").append( threadInfo.getThreadId()).append(" ThreadState=").append(threadInfo.getThreadState());
String lockOwnerStr = "";
if (threadInfo.getLockOwnerName() != null) {
lockOwnerStr = threadInfo.getLockOwnerName() + "\" ThreadId=" + threadInfo.getLockOwnerId();
}
if (threadInfo.isSuspended()) {
sb.append(" (suspended)");
}
if (threadInfo.isInNative()) {
sb.append(" (in native)");
}
sb.append('\n');
StackTraceElement[] stackTrace = threadInfo.getStackTrace();
int i = 0;
for (; i < stackTrace.length && i < MAX_DEPTH; i++) {
for (MonitorInfo mi : threadInfo.getLockedMonitors()) {
if (mi.getLockedStackDepth() == i) {
sb.append("ERROR\t- SuccessLock:"); // 当前拿到的锁的信息
sb.append(mi);
sb.append('\n');
}
}
if (i == 0 && threadInfo.getLockInfo() != null) {
State ts = threadInfo.getThreadState();
switch (ts) {
case BLOCKED:
sb.append("ERROR\t- FailLock:"); // 当前无法获得的锁信息
sb.append(getLockOwnerStr(threadInfo, lockOwnerStr));
sb.append('\n');
break;
case WAITING:
sb.append("ERROR\t- waiting on:");
sb.append(getLockOwnerStr(threadInfo, lockOwnerStr));
sb.append('\n');
break;
case TIMED_WAITING:
sb.append("ERROR\t- timed waiting on:");
sb.append(getLockOwnerStr(threadInfo, lockOwnerStr));
sb.append('\n');
break;
default:
}
}
StackTraceElement ste = stackTrace[i];
sb.append("ERROR\t- ").append(ste.toString()).append("\n"); // 发现死锁的代码位置
}
if (i < stackTrace.length) {
sb.append("ERROR\t...");
sb.append('\n');
}
// 获取当前获得的重入锁
LockInfo[] locks = threadInfo.getLockedSynchronizers();
if (locks.length > 0) {
sb.append("ERROR\n\tNumber of LockedSynchronizer = ").append(locks.length);
sb.append('\n');
for (LockInfo li : locks) {
sb.append("ERROR\t- ").append(li);
sb.append('\n');
}
}
sb.append("\n\n");
}
private String getLockOwnerStr(ThreadInfo threadInfo, String lockOwnerStr) {
return threadInfo.getLockInfo() + " CurOwnerThread:" + lockOwnerStr; // 当前持有的线程
}
/**
* 挂起当前线程一段时间。
*/
private synchronized void sleepIdle(long ms) {
try {
this.wait(ms);
} catch (Exception e) {
Log.logException(e);
}
}
}
2)测试用例
package com.example.variable;
import com.example.variable.deadlock.DeadLockCheckThread;
import com.example.variable.deadlock.Student;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class DeadLockCheckTests {
private final DeadLockCheckThread deadLockCheckThread = new DeadLockCheckThread();
@BeforeEach
void setUp() {
deadLockCheckThread.start();
}
@Test
public void test() throws Exception {
Object lock1 = new Object();
Student lock2 = new Student();
new Thread(()->{
synchronized (lock1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (lock2){
System.out.println("thread1 run");
}
}
}, "thread1").start();
new Thread(()->{
synchronized (lock2){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (lock1){
System.out.println("thread2 run");
}
}
}, "thread2").start();
Thread.sleep(Long.MAX_VALUE);
}
}
3)效果
假设线程1和线程2相互争夺出现了死锁,打印结果如下:
1 // 打印了就说明发现了死锁
2 // 死锁的线程1
3 // 当前死锁线程1已经持有的锁
4 // 当前死锁线程1想要竞争但是没有竞争到的锁 + 这个没有竞争到的锁当前被哪个线程2持有
5 // 死锁线程1的代码出现在哪一行
6 // 哪个线程2持有的这个锁