一般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查看死鎖只須要兩步:併發
這樣咱們就能拿到對應進程的線程信息,好比運行上面發生死鎖的代碼,而後執行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檢測不到的死鎖。下面直接看代碼,裏面邏輯摘自《實戰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的鎖,所以死鎖就此發生。所以你們在寫代碼時必定要避免上面的寫法,不然用常規的方法根本監測定位不到死鎖。