如何寫一個jstack檢測不到的死鎖

一般java程序排查死鎖的辦法是經過jstack 打印出線程信息,裏面會直接顯示發生死鎖的狀況。這裏咱們先解釋下查死鎖能夠採用的兩種辦法。而後咱們寫一個用普通的方法檢測不到的死鎖。java

這裏咱們先貼一個簡單的發生死鎖的代碼。多線程

//class A
public class A implements Runnable {
     public void run() {
        synchronized (B.class){
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (A.class){
                System.out.println("A println: i am finished");
            }
        }
    }
}
//class B
public class B implements Runnable {
    public void run() {
        synchronized (A.class){
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (B.class){
                System.out.println("B println: i am finished");
            }
        }
    }
}
//main class
public class ABTest {
    public static void main(String[]args){
        ExecutorService executorService= Executors.newCachedThreadPool();
        executorService.submit(new A());
        executorService.submit(new B());
    }
}

##jstack查死鎖辦法 jstack查看死鎖只須要兩步:併發

  • jps 或者 ps 拿到對應的java進程號<pid>。
  • jstack <pid>

這樣咱們就能拿到對應進程的線程信息,好比運行上面發生死鎖的代碼,而後執行jstack咱們就能夠看到以下信息(省落部分信息)。jvm

"pool-1-thread-2":
	at com.yao.bytecode.B.run(B.java:22)
	- waiting to lock <0x00000007956f8a10> (a java.lang.Class for com.yao.bytecode.B)
	- locked <0x00000007956f4cb8> (a java.lang.Class for com.yao.bytecode.A)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
"pool-1-thread-1":
	at com.yao.bytecode.A.run(A.java:19)
	- waiting to lock <0x00000007956f4cb8> (a java.lang.Class for com.yao.bytecode.A)
	- locked <0x00000007956f8a10> (a java.lang.Class for com.yao.bytecode.B)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

Found one Java-level deadlock:
"pool-1-thread-2":
  waiting to lock monitor 0x00007fb42c020cc8 (object 0x00000007956f8a10, a java.lang.Class),
  which is held by "pool-1-thread-1"
"pool-1-thread-1":
  waiting to lock monitor 0x00007fb42c0234a8 (object 0x00000007956f4cb8, a java.lang.Class),
  which is held by "pool-1-thread-2"

上面的信息很清楚顯示pool-1-thread-1和 pool-1-thread-2在相互等待對方鎖住的鎖。ide

##利用ThreadMXBean 查死鎖函數

這種辦法是經過JMX(Java管理擴展)提供的管理接口ThreadMXBean來查看有沒有死鎖發生。 不瞭解的JMX的基本使用的同窗能夠經過下面的例子瞭解或者直接去搜索瞭解下。放個的小例子,也是從網上搜到的。this

//EchoMBean
public interface EchoMBean {
    public String print(String name);
}
//Echo
public class Echo implements EchoMBean {

    public String print(String name) {
        System.out.println("hi "+ name);
        return "hi "+name;
    }
}
//
public class MbeanTest {
    public static void main(String[]args) throws Exception {
        MBeanServer mBeanServer= ManagementFactory.getPlatformMBeanServer();
       //這裏包名要和實現類Echo的包名一致
        ObjectName name=new ObjectName("com.yao.mbean:type=Echo");
        Echo mbean=new Echo();
        mBeanServer.registerMBean(mbean,name);
        mBeanServer.invoke(name,"print",new Object[]{"hello"},new String[]{"java.lang.String"});
        TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
    }
}

運行上面的代碼,而後打開jconsole 或者jprofiler(須要本身安裝)界面,鏈接到咱們的上面開的程序,而後就能夠經過界面操做咱們注入的管理bean,輸入方法參數點擊執行,咱們就能夠在後臺看到咱們想要的執行。
輸入圖片說明
簡單介紹下Mbean的使用後我繼續說下ThreadMXBean,這個接口咱們主要看findMonitorDeadlockedThreads方法和查看具體線程信息方法getThreadInfo。由於jvm啓動時,會自動把ThreadMXBean的實現類注入到管理平臺中,所以咱們能夠直接經過jprofiler -> MBeans 找到java.lang Threading,而後點擊operation,執行findMonitorDeadlockedThreads。便可看到結果。
輸入圖片說明
而後就能夠拿到發生死鎖的線程id,在經過id 用getThreadInfo 看具體的信息。.net

jstack檢測不到的死鎖

介紹這麼多,咱們還沒說怎麼寫個jstack檢測不到的死鎖。下面直接看代碼,裏面邏輯摘自《實戰Java虛擬機》。直接先上代碼:線程

public class StaticA {
    static {
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            Class.forName("com.yao.bytecode.StaticB");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println("StaticA init OK");
    }
}
public class StaticB {
    static {
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            Class.forName("com.yao.bytecode.StaticA");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println("StaticB init OK");
    }
}

public class StaticABTest extends Thread {
    private String flag;
    public StaticABTest(String flag) {
        this.flag = flag;
    }
    @Override
    public void run() {
        try {
            Class.forName("com.yao.bytecode.Static"+flag);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[]args) throws Exception {
        StaticABTest staticA=new StaticABTest("A");
        StaticABTest staticB=new StaticABTest("B");
        staticA.start();
        staticB.start();
        staticA.join();
        staticB.join();
    }
}

運行上面代碼,會發生死鎖,程序一直運行,而後運行咱們的jstack去獲取thread信息,咱們看不到任何死鎖信息,用ThreadMXBean 也看不到(本質和jstack查死鎖的原理差很少)。這是爲什呢?code

這和JVM初始化java bean的邏輯有關係,咱們都知道JVM加載一個class 會有不少步驟:加載-> 鏈接(驗證,準備,解析)->初始化。在初始化步驟中,JVM會執行類編譯後的cinit函數,而static塊裏的邏輯會被編譯器放到cinit函數中,當JVM執行cinit時會給cinit加上鎖,防止多線程併發執行。所以當staticA staticB 進行初始化時都加上本身初始化的鎖,而後在經過Class.forName去加載對方,所以都想獲取對方要執行cinit的鎖,所以死鎖就此發生。所以你們在寫代碼時必定要避免上面的寫法,不然用常規的方法根本監測定位不到死鎖。

https://my.oschina.net/robinyao/blog/807450

相關文章
相關標籤/搜索