死鎖是指,兩個或多個動做一直在等待其餘動做完成而使得全部動做都始終處在阻塞的狀態。想要在開發階段檢測到死鎖是很是困難的,而想要解除死鎖每每須要從新啓動程序。更糟的是,死鎖一般發生在負載最重的生產過程當中,而想要在測試中發現它,十分不易。之因此這麼說,是由於測試線程之間全部可能的交叉是不現實的。儘管出現了一些靜態分析庫能夠幫助咱們發現可能出現的死鎖,咱們仍是有必要在運行時檢測到死鎖,而且獲得有用的信息,以便咱們解決這個問題或者重啓程序,或者作些其餘的事情。java
在編程中使用ThreadMXBean類來檢測死鎖編程
Java 5引入了ThreadMXBean接口,它提供了多種監視線程的方法。我建議您瞭解全部這些方法,由於當您沒使用外部工具時,它們會爲您提供不少有用的操做以便您監測程序性能。這裏,咱們感興趣的方法是findMonitorDeadlockedThreads,如過您使用的是Java 6,對應的方法是findDeadlockedThreads。兩者的區別的是,findDeadlockedThreads還能夠檢測到owner locks(java.util.concurrent)引發的死鎖,而findMonitorDeadlockedThreads只能檢測monitor locks(例如,同步塊)。因爲保留老版本的方法只是出於兼容性的考慮,因此我將使用新版本的方法。在這裏,編程的思想是把對死鎖的週期性檢測封裝到一個可重用組件裏,以後咱們只需啓動它、隨它去。多線程
一種實現調度的方法是經過執行器框架,即一組良好抽象並易於使用的多線程類。框架
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); this.scheduler.scheduleAtFixedRate(deadlockCheck, period, period, unit);ide
就是那麼簡單,在咱們經過選擇週期和時間單位而設置了一個特定時間後,就獲得了一個週期性調用的線程。接着,咱們想使功用得以拓展從而容許用戶提供在程序檢測到死鎖時所觸發的行爲。最後,咱們須要一個方法來接收用於描述死鎖中全部線程的一系列對象。工具
void handleDeadlock(final ThreadInfo deadlockedThreads);性能
如今,實現死鎖檢測類已經萬事俱備了。測試
public interface DeadlockHandler { void handleDeadlock(final ThreadInfo deadlockedThreads); } public class DeadlockDetector { private final DeadlockHandler deadlockHandler; private final long period; private final TimeUnit unit; private final ThreadMXBean mbean = ManagementFactory.getThreadMXBean; private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); final Runnable deadlockCheck = new Runnable { @Override public void run { long deadlockedThreadIds = DeadlockDetector.this.mbean.findDeadlockedThreads; if (deadlockedThreadIds != null) { ThreadInfo threadInfos = DeadlockDetector.this.mbean.getThreadInfo(deadlockedThreadIds); DeadlockDetector.this.deadlockHandler.handleDeadlock(threadInfos); } } }; public DeadlockDetector(final DeadlockHandler deadlockHandler, final long period, final TimeUnit unit) { this.deadlockHandler = deadlockHandler; this.period = period; this.unit = unit; } public void start { this.scheduler.scheduleAtFixedRate( this.deadlockCheck, this.period, this.period, this.unit); } }ui
讓咱們動手試試。首先,咱們要建立一個handler用來向System.err輸出死鎖線程的信息。在現實場景中,咱們能夠用它發送郵件,好比:this
public class DeadlockConsoleHandler implements DeadlockHandler { @Override public void handleDeadlock(final ThreadInfo deadlockedThreads) { if (deadlockedThreads != null) { System.err.println("Deadlock detected!"); Map stackTraceMap = Thread.getAllStackTraces; for (ThreadInfo threadInfo : deadlockedThreads) { if (threadInfo != null) { for (Thread thread : Thread.getAllStackTraces.keySet) { if (thread.getId == threadInfo.getThreadId) { System.err.println(threadInfo.toString.trim); for (StackTraceElement ste : thread.getStackTrace) { System.err.println("t" + ste.toString.trim); } } } } } } } }
這一過程在全部的堆棧追蹤中反覆進行併爲每一個線程信息打印對應的堆棧蹤影。經過這種方式,咱們能夠準確知道每一個線程等待的位置和對象。但這個方法有一個缺陷——當一個線程只是暫時等待時,可能會被看成一個暫時的死鎖,從而引起錯誤的警報。出於此,當咱們處理死鎖時,原始線程不能繼續存在而findDeadlockedThreads方法會返回沒有此類線程。爲了不可能出現的NullPointerException,咱們須要警戒這種狀況。最後,讓咱們促成一個死鎖來看看系統是如何運行的。
DeadlockDetector deadlockDetector = new DeadlockDetector(new DeadlockConsoleHandler, 5, TimeUnit.SECONDS); deadlockDetector.start; final Object lock1 = new Object; final Object lock2 = new Object; Thread thread1 = new Thread(new Runnable { @Override public void run {synchronized (lock1) { System.out.println("Thread1 acquired lock1"); try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException ignore) { } synchronized (lock2) { System.out.println("Thread1 acquired lock2"); } } } }); thread1.start; Thread thread2 = new Thread(new Runnable { @Override public void run { synchronized (lock2) { System.out.println("Thread2 acquired lock2"); synchronized (lock1) { System.out.println("Thread2 acquired lock1"); } } } }); thread2.start;
輸出:
Thread1 acquired lock1 Thread2 acquired lock2 Deadlock detected! 「Thread-1」 Id=11 BLOCKED on java.lang.Object@68ab95e6 owned by 「Thread-0」 Id=10 deadlock.DeadlockTester$2.run(DeadlockTester.java:42) java.lang.Thread.run(Thread.java:662) 「Thread-0」 Id=10 BLOCKED on java.lang.Object@58fe64b9 owned by 「Thread-1」 Id=11 deadlock.DeadlockTester$1.run(DeadlockTester.java:28) java.lang.Thread.run(Thread.java:662)
記住,死鎖檢測的開銷可能會很大,你須要用你的程序來測試一下你是否真的須要死鎖檢測以及多久檢測一次。我建議死鎖檢測的時間間隔至少爲幾分鐘,由於更加頻繁的檢測並無太大的意義,緣由是咱們並無一個復原計劃,咱們能作的只是調試和處理錯誤或者重啓程序並祈禱不會再次發生死鎖。若是你有關於解決死鎖問題的好建議或者關於這個解決方案的疑問,請在下面留言。