利用ThreadMXBean實現(xiàn)檢測死鎖

在使用JConsole的時候,在線程頁下,可以看到一個檢測死鎖按鈕,很好奇它是如何獲取死鎖線程的。同時檢測死鎖算法也是操作系統(tǒng)的重要算法之一,本文在參考了JConsole源碼的基礎上來實現(xiàn)檢測死鎖的功能。
獲取源碼
https://github.com/maoturing/bescode/tree/master/src/com/requirement/detectdeadlock

1.檢測死鎖類的屬性和方法

public class DeadlockDetector{

  //獲取ThreadMXBean 
  private final ThreadMXBean mBean = ManagementFactory.getThreadMXBean();

  //處理檢測到的死鎖線程
  public void handleDeadLock(ThreadInfo[] deadLockThreads);

  //得到線程鎖定的對象
  public void getThreadLock(long selected);

  //得到線程和線程所在的組,K為線程Id,V為所在的組,組以死鎖劃分,一個死鎖為一組
  public Map getDeadlockedGroup() throws IOException;

2.獲取死鎖線程

// 查找因為等待獲得對象監(jiān)視器或可擁有同步器而處于死鎖狀態(tài)的線程循環(huán)。返回線程ID
long[] ids = mBean.findDeadlockedThreads(); 

// 根據(jù)死鎖線程id得到死鎖線程信息,參數(shù)2代表的是獲取同步監(jiān)視器,后面獲取線程鎖定對象時使用
ThreadInfo[] threadInfos = mBean.getThreadInfo(ids , true, false); 

3.處理死鎖線程

public void handleDeadLock(ThreadInfo[] deadLockThreads) {
        if (deadLockThreads != null) {
            System.err.println("Deadlock detected!");

            for (ThreadInfo threadInfo : deadLockThreads) {
                if (threadInfo != null) {
                    // SecurityException
                    for (Thread thread : Thread.getAllStackTraces().keySet()) {
                        if (thread.getId() == threadInfo.getThreadId()) {
                            System.out.println("id:" + threadInfo.getThreadId());
                            System.out.println("名稱:" + threadInfo.getThreadName());
                            System.out.print("狀態(tài):" + threadInfo.getLockName());
                            System.out.println("上的" + threadInfo.getThreadState());
                            System.out.println("擁有者:" + threadInfo.getLockOwnerName());
                            System.out.println("總阻止數(shù):" + threadInfo.getBlockedCount());
                            System.out.println("總等待數(shù):" + threadInfo.getWaitedCount());
                            System.out.println("狀態(tài):" + threadInfo.toString());

                            String name = mBean.getThreadInfo(threadInfo.getLockOwnerId()).getLockName();
                            System.out.println("已鎖定:" + name);
                            int i = 0;

                            MonitorInfo[] monitors = threadInfo.getLockedMonitors();

                            for (StackTraceElement ste : thread.getStackTrace()) {
                                System.err.println("堆棧深度:"+thread.getStackTrace().length);
                                System.err.println("堆棧信息:"+ste.toString());

                                System.out.println("拼接的堆棧信息:"+ste.getClassName() + ste.getMethodName() + ste.getFileName()
                                        + ste.getLineNumber());
                                selectThread(threadInfo.getThreadId());
                                            }
                        }

                    }
                    System.out.println("==================");
                }
            }

        } else {
            System.out.println("未檢測到死鎖線程");
        }

    }

4.得到線程鎖定的對象
首先需要得到線程對象,mBean.getThreadInfo(deadlockedThreads, true, false)方法的第二個參數(shù)為true表示獲得同步監(jiān)視器,

public void getThreadLock(long selected) {
        final long threadID = selected;
        StringBuilder sb = new StringBuilder();
        ThreadInfo ti = null;
        MonitorInfo[] monitors = null;
            for (ThreadInfo info : infos) {
                if (info.getThreadId() == threadID) {
                    ti = info;
                    monitors = info.getLockedMonitors();
                    break;
                }
            }
            System.out.println("support");
        if (ti != null) {
            int index = 0;
            if (monitors != null) {
                for (MonitorInfo mi : monitors) {
                    if (mi.getLockedStackDepth() == index) {
                        System.out.println("已鎖定:" + mi.toString());
                    }
                }
                index++;
            }
        }

    }

5.得到線程所在的組(檢測死鎖算法)
線程所在的組的區(qū)分標準是線程是否屬于同一個死鎖,所謂死鎖是指兩個或兩個以上的線程,互相競爭資源導致的一種阻塞現(xiàn)象,最簡單的例子就是線程 T1 已鎖定B對象,然后去請求A對象,而線程 T2 已鎖定A對象,然后去請求B對象,導致二者一直阻塞下去。

兩個線程競爭資源導致的死鎖.png

當然,實際可能出現(xiàn)的情況會更復雜,我們?nèi)绾蝸砼袛嗄男┚€程屬于同一個死鎖呢?
在下圖中可以看出,無論是哪種死鎖形式,都會形成一個閉環(huán)。根據(jù)這個特點,我們可以將死鎖抽象為有向圖,多個死鎖就是多個有向圖,我們改造一下有向圖的遍歷算法即可得到所有的死鎖以及死鎖擁有的線程。


三種死鎖形式

首先遍歷所有被阻塞的線程T1~T7,第一次先得到線程T1,由于T1被B對象阻塞,我們使用getLockOwnerId()方法獲得B對象的擁有者線程T2,并將T1的訪問標志visited[j]置為true,繼續(xù)執(zhí)行.......,直到得到了線程T3,用getLockOwnerId()方法獲得線程T1,此時發(fā)現(xiàn)線程T1的訪問標志visited[j]已經(jīng)為true,意味著我們已經(jīng)完成了一個死鎖的遍歷,之前遍歷過的線程組成了1個死鎖,此時標志死鎖個數(shù)的gid++,繼續(xù)遍歷下一個死鎖。

    public Map getDeadlockedGroup() throws IOException {

        long[] ids = mBean.findDeadlockedThreads();
        if (ids == null) {
            return null;
        }
        ThreadInfo[] infos = mBean.getThreadInfo(ids, Integer.MAX_VALUE);

        List<Long[]> dcycles = new ArrayList<Long[]>();
        List<Long> cycle = new ArrayList<Long>();
        Map<Long, Integer> map = new HashMap<Long, Integer>();
        int gid = 1;
        boolean[] visited = new boolean[ids.length];

        int index = -1; // Index into arrays
        while (true) {
            if (index < 0) {
                if (map.size() > 0) {
                    // a cycle found
                    // dcycles.add(cycle.toArray(new Long[0]));
                    gid++;
                    // cycle = new ArrayList<Long>();
                }
                // start a new cycle from a non-visited thread
                for (int j = 0; j < ids.length; j++) {
                    if (!visited[j]) {
                        index = j;
                        visited[j] = true;
                        break;
                    }
                }

                // 當所有線程均被訪問過,退出while循環(huán)
                if (index < 0) {
                    // done
                    break;
                }
            }

            // cycle.add(ids[index]);
            map.put(ids[index], gid);
            long nextThreadId = infos[index].getLockOwnerId();

            for (int j = 0; j < ids.length; j++) {
                ThreadInfo ti = infos[j];
                if (ti.getThreadId() == nextThreadId) {
                    if (visited[j]) {
                        index = -1;
                    } else {
                        index = j;
                        visited[j] = true;
                    }
                    break;
                }
            }
        }
        //線程id為key,所在死鎖組為value
        return map;
    }

對于死鎖分組,由于遍歷算法的原因,可能會出現(xiàn)一個線程組成的死鎖的小bug,比如“圖-三種死鎖形式”中的第三種情況,如果從T8開始遍歷,那么T8和T7組成一個死鎖,T6單獨組成一個死鎖,這種情況極為少見,JConsole也是這樣處理,所以這里就不深入追究了。以上,就是java利用ThreadMXBean獲取線程死鎖的所有內(nèi)容。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,896評論 0 11
  • Java SE 基礎: 封裝、繼承、多態(tài) 封裝: 概念:就是把對象的屬性和操作(或服務)結合為一個獨立的整體,并盡...
    Jayden_Cao閱讀 2,235評論 0 8
  • Java-Review-Note——4.多線程 標簽: JavaStudy PS:本來是分開三篇的,后來想想還是整...
    coder_pig閱讀 1,772評論 2 17
  • 從三月份找實習到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時芥藍閱讀 42,792評論 11 349
  • 剛開始被男神拉進群里的時候挺意外的,因為不覺得自己做得好,尤其是在目標的實現(xiàn)上,感覺自己這一期相比較上一期,除了保...
    一只特栗獨行閱讀 501評論 0 0

友情鏈接更多精彩內(nèi)容