如何通過程式設計發現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);
就是那麼簡單,在我們通過選擇週期和時間單位而設定了一個特定時間後,就得到了一個週期性呼叫的執行緒。接著,我們想使功用得以擴充從而允許使用者提供在程式檢測到死鎖時所觸發的行為。最後,我們需要一個方法來接收用於描述死鎖中所有執行緒的一系列物件。
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); } }
讓我們動手試試。首先,我們要建立一個handler用來向System.err輸出死鎖執行緒的資訊。在現實場景中,我們可以用它傳送郵件,比如:
public class DeadlockConsoleHandler implements DeadlockHandler { @Override public void handleDeadlock(final ThreadInfo[] deadlockedThreads) { if (deadlockedThreads != null) { System.err.println("Deadlock detected!"); Map<Thread, StackTraceElement[]> 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)
記住,死鎖檢測的開銷可能會很大,你需要用你的程式來測試一下你是否真的需要死鎖檢測以及多久檢測一次。我建議死鎖檢測的時間間隔至少為幾分鐘,因為更加頻繁的檢測並沒有太大的意義,原因是我們並沒有一個復原計劃,我們能做的只是除錯和處理錯誤或者重啟程式並祈禱不會再次發生死鎖。如果你有關於解決死鎖問題的好建議或者關於這個解決方案的疑問,請在下面留言。
相關文章
- java程式設計思想之併發(死鎖)Java程式設計
- Java併發程式設計實戰--通過執行緒轉儲資訊來分析死鎖Java程式設計執行緒
- Java併發程式設計實戰(4)- 死鎖Java程式設計
- java如何避免程式死鎖Java
- Java 併發程式設計:如何防止線上程阻塞與喚醒時死鎖Java程式設計
- Java併發程式設計實戰 04死鎖了怎麼辦?Java程式設計
- [Java併發]避免死鎖Java
- 通過ASP.NET程式設計進行遠端殺死程式ASP.NET程式設計
- Java鎖——死鎖Java
- 通過socket實現DUP程式設計程式設計
- 如何順利通過程式設計面試程式設計面試
- 面試:什麼是死鎖,如何避免或解決死鎖;MySQL中的死鎖現象,MySQL死鎖如何解決面試MySql
- Java併發程式設計實戰--協作物件間的死鎖與開放呼叫Java程式設計物件
- Java併發程式設計-鎖及併發容器Java程式設計
- 通過 sysprocesses 解決Sql死鎖問題SQL
- 併發程式設計之 Java 三把鎖程式設計Java
- 併發程式設計之臨界區\阻塞\非阻塞\死鎖\飢餓\活鎖程式設計
- 如何通過sid查詢造成死鎖的sql語句,並殺死會話SQL會話
- 併發程式設計(3)執行緒同步的方式及死鎖程式設計執行緒
- Java 實現執行緒死鎖Java執行緒
- 《JAVA併發程式設計實戰》顯式鎖Java程式設計
- Java併發程式設計——深入理解自旋鎖Java程式設計
- Java併發程式設計實戰--閉鎖 CountDownLatchJava程式設計CountDownLatch
- 通過電子郵件發現優秀的程式設計師程式設計師
- 通過 Socket 實現 TCP 程式設計入門TCP程式設計
- 通過 Socket 實現 UDP 程式設計 入門UDP程式設計
- 殺死Oracle死鎖程式Oracle
- Java併發程式設計之鎖機制之(ReentrantLock)重入鎖Java程式設計ReentrantLock
- 什麼是死鎖?如何解決死鎖?
- Java併發程式設計之鎖機制之AQSJava程式設計AQS
- 例項詳解 Java 死鎖與破解死鎖Java
- python多執行緒程式設計4: 死鎖和可重入鎖Python執行緒程式設計
- Java多執行緒程式設計(同步、死鎖、生產消費者問題)Java執行緒程式設計
- 程式設計師如何一次通過軟考?程式設計師
- JAVA實現網路程式設計之併發程式設計Java程式設計
- Java 中的死鎖Java
- Java併發程式設計(05):悲觀鎖和樂觀鎖機制Java程式設計
- 如何寫一段死鎖程式碼