死锁检测 及其测试用例

发布于:2025-08-31 ⋅ 阅读:(21) ⋅ 点赞:(0)

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持有的这个锁


网站公告

今日签到

点亮在社区的每一天
去签到