如何通過編程發現Java死鎖

來源:文萃谷 2.29W

死鎖,是指兩個或多個動作一直在等待其他動作完成而使得所有動作都始終處在阻塞的狀態。如何通過編程發現Java死鎖?下面就一起來了解看看吧!

如何通過編程發現Java死鎖

死鎖是指,兩個或多個動作一直在等待其他動作完成而使得所有動作都始終處在阻塞的狀態。想要在開發階段檢測到死鎖是非常困難的,而想要解除死鎖往往需要重新啟動程序。更糟的是,死鎖通常發生在負載最重的生產過程中,而想要在測試中發現它,十分不易。之所以這麼説,是因為測試線程之間所有可能的交叉是不現實的。儘管出現了一些靜態分析庫可以幫助我們發現可能出現的死鎖,我們還是有必要在運行時檢測到死鎖,並且得到有用的信息,以便我們解決這個問題或者重啟程序,或者做些其他的事情。

在編程中使用ThreadMXBean類來檢測死鎖

Java 5引入了ThreadMXBean接口,它提供了多種監視線程的方法。我建議您瞭解所有這些方法,因為當您沒使用外部工具時,它們會為您提供很多有用的操作以便您監測程序性能。這裏,我們感興趣的方法是findMonitorDeadlockedThreads,如過您使用的是Java 6,對應的方法是findDeadlockedThreads。二者的區別的是,findDeadlockedThreads還可以檢測到owner locks(urrent)引起的死鎖,而findMonitorDeadlockedThreads只能檢測monitor locks(例如,同步塊)。由於保留老版本的方法只是出於兼容性的考慮,所以我將使用新版本的方法。在這裏,編程的思想是把對死鎖的週期性檢測封裝到一個可重用組件裏,之後我們只需啟動它、隨它去。

一種實現調度的方法是通過執行器框架,即一組良好抽象並易於使用的多線程類。

ScheduledExecutorService scheduler = cheduledThreadPool(1);

duleAtFixedRate(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 = hreadMXBean();

private final ScheduledExecutorService scheduler =

cheduledThreadPool(1);

final Runnable deadlockCheck = new Runnable() {

@Override

public void run() {

long[] deadlockedThreadIds = DeadlockedThreads();

if (deadlockedThreadIds != null) {

ThreadInfo[] threadInfos =

hreadInfo(deadlockedThreadIds);

leDeadlock(threadInfos);

}

}

};

public DeadlockDetector(final DeadlockHandler deadlockHandler,

final long period, final TimeUnit unit) {

lockHandler = deadlockHandler;

od = period;

= unit;

}

public void start() {

duleAtFixedRate(

lockCheck, od, od, );

}

}

讓我們動手試試。首先,我們要創建一個handler用來向輸出死鎖線程的信息。在現實場景中,我們可以用它發送郵件,比如:

public class DeadlockConsoleHandler implements DeadlockHandler {

@Override

public void handleDeadlock(final ThreadInfo[] deadlockedThreads) {

if (deadlockedThreads != null) {

tln("Deadlock detected!");

Map<Thread, StackTraceElement[]> stackTraceMap = llStackTraces();

for (ThreadInfo threadInfo : deadlockedThreads) {

if (threadInfo != null) {

for (Thread thread : llStackTraces()et()) {

if (d() == hreadId()) {

tln(ring()());

for (StackTraceElement ste : tackTrace()) {

tln("t" + ring()());

}

}

}

}

}

}

}

}

這一過程在所有的堆棧追蹤中反覆進行併為每個線程信息打印對應的堆棧蹤跡。通過這種方式,我們可以準確知道每個線程等待的位置和對象。但這個方法有一個缺陷——當一個線程只是暫時等待時,可能會被當作一個暫時的死鎖,從而引發錯誤的.警報。出於此,當我們處理死鎖時,原始線程不能繼續存在而findDeadlockedThreads方法會返回沒有此類線程。為了避免可能出現的NullPointerException,我們需要警惕這種情況。最後,讓我們促成一個死鎖來看看系統是如何運行的。

DeadlockDetector deadlockDetector = new DeadlockDetector(new DeadlockConsoleHandler(), 5, NDS);

t();

final Object lock1 = new Object();

final Object lock2 = new Object();

Thread thread1 = new Thread(new Runnable() {

@Override

public void run() {

synchronized (lock1) {

tln("Thread1 acquired lock1");

try {

p(500);

} catch (InterruptedException ignore) {

}

synchronized (lock2) {

tln("Thread1 acquired lock2");

}

}

}

});

t();

Thread thread2 = new Thread(new Runnable() {

@Override

public void run() {

synchronized (lock2) {

tln("Thread2 acquired lock2");

synchronized (lock1) {

tln("Thread2 acquired lock1");

}

}

}

});

t();

輸出:

Thread1 acquired lock1

Thread2 acquired lock2

Deadlock detected!

“Thread-1” Id=11 BLOCKED on ct@68ab95e6 owned by “Thread-0” Id=10

lockTester$()

()

“Thread-0” Id=10 BLOCKED on ct@58fe64b9 owned by “Thread-1” Id=11

lockTester$()

()

記住,死鎖檢測的開銷可能會很大,你需要用你的程序來測試一下你是否真的需要死鎖檢測以及多久檢測一次。我建議死鎖檢測的時間間隔至少為幾分鐘,因為更加頻繁的檢測並沒有太大的意義,原因是我們並沒有一個復原計劃,我們能做的只是調試和處理錯誤或者重啟程序並祈禱不會再次發生死鎖。

熱門標籤