将线程死锁检测集成进arthas以提高定位效率

本文涉及的产品
可观测可视化 Grafana 版,10个用户账号 1个月
可观测监控 Prometheus 版,每月50GB免费额度
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 将线程死锁检测集成进arthas以提高定位效率

背景介绍

在日常排查过程中,有遇到线程死锁的问题,系统自带的jstack可以用来定位发生死锁的线程,而arthas作为日常分析排查问题不可缺少的利器,如果能够将线程死锁检测集成进arthas,线程死锁分析排查会更加方便和高效。

实现思路

jstack实现方式

以下程序主要由两个部分组成:

  1. 由synchronized和java.util.concurrent.Lock引起的线程死锁示例
  2. 线程死锁检测实现逻辑

产生线程死锁逻辑

import java.util.concurrent.locks.ReentrantLock;
public class DeadlockBuilder {
    private static final ReentrantLock REENTRANT_LOCK_A = new ReentrantLock();
    private static final ReentrantLock REENTRANT_LOCK_B = new ReentrantLock();
    public static void deadlockByMonitor() {
        Object a = new Object();
        Object b = new Object();
        Thread threadA = new Thread(() -> {
            synchronized (a) {
                sleep();
                synchronized (b) {
                }
            }
        });
        threadA.setName("Thread_Monitor_A");
        threadA.start();
        Thread threadB = new Thread(() -> {
            synchronized (b) {
                sleep();
                synchronized (a) {
                }
            }
        });
        threadB.setName("Thread_Monitor_B");
        threadB.start();
    }
    public static void deadlockBySynchronizer() {
        Thread threadA = new Thread(() -> {
            REENTRANT_LOCK_A.lock();
            try {
                sleep();
                REENTRANT_LOCK_B.lock();
                try {
                } finally {
                    REENTRANT_LOCK_B.unlock();
                }
            } finally {
                REENTRANT_LOCK_A.unlock();
            }
        });
        threadA.setName("Thread_Synchronizer_A");
        threadA.start();
        Thread threadB = new Thread(() -> {
            REENTRANT_LOCK_B.lock();
            try {
                sleep();
                REENTRANT_LOCK_A.lock();
                try {
                } finally {
                    REENTRANT_LOCK_A.unlock();
                }
            } finally {
                REENTRANT_LOCK_B.unlock();
            }
        });
        threadB.setName("Thread_Synchronizer_B");
        threadB.start();
    }
    private static void sleep() {
        try {
            Thread.sleep(100L);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

死锁检测实现逻辑

import java.lang.management.ManagementFactory;
import java.lang.management.MonitorInfo;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.lang.management.LockInfo;
import java.util.*;
public class DeadlockDetector {
    private static final ThreadMXBean THREADMX_BEAN = ManagementFactory.getThreadMXBean();
    public static void main(String[] args) throws Exception {
        System.out.println(ManagementFactory.getRuntimeMXBean().getName());
        DeadlockBuilder.deadlockByMonitor();
        DeadlockBuilder.deadlockBySynchronizer();
        Thread.sleep(1000);
        DeadlockInfo deadlock = detect();
        render(deadlock);
    }
    private static DeadlockInfo detect() {
        int globalDfn = 0, thisDfn;
        ThreadInfo currentThread;
        ThreadInfo previousThread;
        LockInfo waitingToLockInfo;
        ThreadInfo[] infos = THREADMX_BEAN.dumpAllThreads(THREADMX_BEAN.isObjectMonitorUsageSupported(),
                THREADMX_BEAN.isSynchronizerUsageSupported());
        Map<ThreadInfo, Integer> threadTable = new HashMap<>();
        Map<Integer, ThreadInfo> ownerThreadPerLock = new HashMap<>();
        for (ThreadInfo threadInfo : infos) {
            threadTable.put(threadInfo, -1);
            for (MonitorInfo monitorInfo : threadInfo.getLockedMonitors()) {
                ownerThreadPerLock.putIfAbsent(monitorInfo.getIdentityHashCode(), threadInfo);
            }
            for (LockInfo lockedSync : threadInfo.getLockedSynchronizers()) {
                ownerThreadPerLock.putIfAbsent(lockedSync.getIdentityHashCode(), threadInfo);
            }
        }
        DeadlockInfo deadlock = new DeadlockInfo();
        deadlock.setOwnerThreadPerLock(ownerThreadPerLock);
        for (Map.Entry<ThreadInfo, Integer> e : threadTable.entrySet()) {
            Integer value = e.getValue();
            if (value >= 0) {
                // this thread was already visited
                continue;
            }
            thisDfn = globalDfn;
            ThreadInfo thread = e.getKey();
            previousThread = thread;
            waitingToLockInfo = thread.getLockInfo();
            while (waitingToLockInfo != null) {
                currentThread = ownerThreadPerLock.get(waitingToLockInfo.getIdentityHashCode());
                if (currentThread == null) {
                    // No dependency on another thread
                    break;
                }
                Integer val = threadTable.get(thread);
                int dfn = (val != null) ? val : -1;
                if (dfn < 0) {
                    // First visit to this thread
                    threadTable.put(currentThread, globalDfn++);
                } else if (dfn < thisDfn) {
                    // Thread already visited, and not on a (new) cycle
                    break;
                } else if (currentThread == previousThread) {
                    // Self-loop, ignore
                    break;
                } else {
                    // We have a (new) cycle
                    deadlock.getThreads().add(currentThread);
                    break;
                }
                previousThread = currentThread;
                waitingToLockInfo = currentThread.getLockInfo();
            }
        }
        return deadlock;
    }
    public static void render(DeadlockInfo deadlock){
        Map<Integer, ThreadInfo> ownerThreadPerLock = deadlock.getOwnerThreadPerLock();
        for(ThreadInfo thread : deadlock.getThreads()){
            LockInfo waitingToLockInfo;
            ThreadInfo currentThread = thread;
            System.out.println("Found one Java-level deadlock:");
            System.out.println("=============================");
            do{
                System.out.println(currentThread.getThreadName() + "(" + currentThread.getThreadId() + "):");
                waitingToLockInfo = currentThread.getLockInfo();
                if(waitingToLockInfo != null){
                    System.out.println("  waiting to lock info @" + waitingToLockInfo + ",");
                    System.out.print("  which is held by ");
                    currentThread = ownerThreadPerLock.get(waitingToLockInfo.getIdentityHashCode());
                    System.out.println(currentThread.getThreadName());
                }
            }while (!currentThread.equals(thread));
            System.out.println();
        }
        int numberOfDeadlocks = deadlock.getThreads().size();
        switch (numberOfDeadlocks) {
            case 0:
                System.out.println("No deadlocks found.");
                break;
            case 1:
                System.out.println("Found a total of 1 deadlock.");
                break;
            default:
                System.out.println("Found a total of " + numberOfDeadlocks + " deadlocks.");
                break;
        }
    }
}
class DeadlockInfo{
    private List<ThreadInfo> threads = new LinkedList<>();
    private Map<Integer, ThreadInfo> ownerThreadPerLock = new HashMap<>();
    public Map<Integer, ThreadInfo> getOwnerThreadPerLock() {
        return ownerThreadPerLock;
    }
    public void setOwnerThreadPerLock(Map<Integer, ThreadInfo> ownerThreadPerLock) {
        this.ownerThreadPerLock = ownerThreadPerLock;
    }
    public List<ThreadInfo> getThreads() {
        return threads;
    }
    public void setThreads(List<ThreadInfo> threads) {
        this.threads = threads;
    }
}

直接运行上面的程序就可以看到效果了。

JMX实现方式

import java.lang.management.*;
public class ThreadMXDeadlockDetector {
    private static final ThreadMXBean THREADMX_BEAN = ManagementFactory.getThreadMXBean();
    public static void main(String[] args) throws Exception {
        System.out.println(ManagementFactory.getRuntimeMXBean().getName());
        DeadlockBuilder.deadlockByMonitor();
        DeadlockBuilder.deadlockBySynchronizer();
        Thread.sleep(1000);
        DeadlockInfo deadlock = detect();
        DeadlockDetector.render(deadlock);
    }
    private static DeadlockInfo detect(){
        DeadlockInfo deadlockInfo = new DeadlockInfo();
        long[] ids = THREADMX_BEAN.findDeadlockedThreads();
        if(ids == null){
            return deadlockInfo;
        }
        ThreadInfo[] threads = THREADMX_BEAN.getThreadInfo(ids,
                THREADMX_BEAN.isObjectMonitorUsageSupported(),THREADMX_BEAN.isSynchronizerUsageSupported());
        for(ThreadInfo threadInfo : threads){
            LockInfo lockInfo = threadInfo.getLockInfo();
            if(!deadlockInfo.getOwnerThreadPerLock().containsKey(lockInfo.getIdentityHashCode())){
                deadlockInfo.getThreads().add(threadInfo);
            }
            for (MonitorInfo monitorInfo : threadInfo.getLockedMonitors()) {
                deadlockInfo.getOwnerThreadPerLock().putIfAbsent(monitorInfo.getIdentityHashCode(), threadInfo);
            }
            for (LockInfo lockedSync : threadInfo.getLockedSynchronizers()) {
                deadlockInfo.getOwnerThreadPerLock().putIfAbsent(lockedSync.getIdentityHashCode(), threadInfo);
            }
        }
        return deadlockInfo;
    }
}

集成进arthas以提高排查效率

arthas jvm命令显示了死锁线程个数,线程名称、线程ID并没有展示出来,如果想知道死锁线程具体信息需要再使用jstack来分析;

如果死锁线程信息可以在arthas中展示出来可以提高问题排查效率。

集成进arthas主要涉及以下几个类的修改:

  1. ThreadCommand中增加线程死锁检测指令@Option(shortName = “d”, longName = “deadlock”, flag = true)
  2. ThreadUtil中增加线程死锁检测的实现方法
  3. ThreadView中增加线程死锁检测的结果展示逻辑

然后编译打包,运行上面死锁生成的代码,使用arthas thread -d命令效果如下:

总结

  1. 构造由synchronized和java.util.concurrent.Lock引起线程死锁的示例;
  2. jstack及JMX实现线程死锁检测的逻辑;
  3. 将线程死锁检测集成进arthas。
目录
相关文章
|
1月前
线程CPU异常定位分析
【10月更文挑战第3天】 开发过程中会出现一些CPU异常升高的问题,想要定位到具体的位置就需要一系列的分析,记录一些分析手段。
61 0
|
1月前
|
Java 数据库连接 数据库
不同业务使用同一个线程池发生死锁的技术探讨
【10月更文挑战第6天】在并发编程中,线程池是一种常用的优化手段,用于管理和复用线程资源,减少线程的创建和销毁开销。然而,当多个不同业务场景共用同一个线程池时,可能会引发一系列并发问题,其中死锁就是最为严重的一种。本文将深入探讨不同业务使用同一个线程池发生死锁的原因、影响及解决方案,旨在帮助开发者避免此类陷阱,提升系统的稳定性和可靠性。
47 5
|
1月前
|
安全 Java 程序员
【多线程-从零开始-肆】线程安全、加锁和死锁
【多线程-从零开始-肆】线程安全、加锁和死锁
43 0
|
3月前
|
监控 安全 Java
Java多线程调试技巧:如何定位和解决线程安全问题
Java多线程调试技巧:如何定位和解决线程安全问题
131 2
|
3月前
|
安全 算法 Java
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)(下)
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)
77 6
|
3月前
|
存储 安全 Java
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)(中)
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)
86 5
|
3月前
|
存储 安全 Java
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)(上)
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)
84 3
|
4月前
|
Java
Jstack 查看线程状态及定位占用 cpu 较高的 java 线程
Jstack 查看线程状态及定位占用 cpu 较高的 java 线程
596 2
|
3月前
|
Java
Java多线程-死锁的出现和解决
死锁是指多线程程序中,两个或以上的线程在运行时因争夺资源而造成的一种僵局。每个线程都在等待其中一个线程释放资源,但由于所有线程都被阻塞,故无法继续执行,导致程序停滞。例如,两个线程各持有一把钥匙(资源),却都需要对方的钥匙才能继续,结果双方都无法前进。这种情况常因不当使用`synchronized`关键字引起,该关键字用于同步线程对特定对象的访问,确保同一时刻只有一个线程可执行特定代码块。要避免死锁,需确保不同时满足互斥、不剥夺、请求保持及循环等待四个条件。
|
3月前
|
Java 测试技术 PHP
父子任务使用不当线程池死锁怎么解决?
在Java多线程编程中,线程池有助于提升性能与资源利用效率,但若父子任务共用同一池,则可能诱发死锁。本文通过一个具体案例剖析此问题:在一个固定大小为2的线程池中,父任务直接调用`outerTask`,而`outerTask`再次使用同一线程池异步调用`innerTask`。理论上,任务应迅速完成,但实际上却超时未完成。经由`jstack`输出的线程调用栈分析发现,线程陷入等待状态,形成“死锁”。原因是子任务需待父任务完成,而父任务则需等待子任务执行完毕以释放线程,从而相互阻塞。此问题在测试环境中不易显现,常在生产环境下高并发时爆发,重启或扩容仅能暂时缓解。