死鎖應該可以說是并發程式設計中比較常見的一種情況,可以說如果程式産生了死鎖那将會對程式帶來緻命的影響;是以排查定位、修複死鎖至關重要;
我們都知道死鎖是由于多個對象或多個線程之間互相需要對方鎖持有的鎖而又沒有釋放對方所持有的鎖,導緻雙方都永久處于阻塞狀态;

死鎖檢測
需要檢測死鎖肯定要先有死鎖出現,下面的demo模拟了一個死鎖的産生;
public class DeadlockDemo extends Thread {
private BaseObj first;
private BaseObj second;
public DeadlockDemo(String name, BaseObj first, BaseObj second) {
super(name);
this.first = first;
this.second = second;
}
public void reentrantLock() throws InterruptedException {
first.lock();
System.out.println(String.format("%s 持有:%s 對象鎖,等待擷取:%s對象鎖", this.getName(), first, second));
second.lock();
first.unlock();
second.unlock();
}
@Override
public void run() {
try {
reentrantLock();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
ObjOne one = new ObjOne();
ObjTwo two = new ObjTwo();
DeadlockDemo thread1 = new DeadlockDemo("Thread1", one, two);
DeadlockDemo thread2 = new DeadlockDemo("Thread2", two, one);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
}
}
運作上面的demo将看到程式被阻塞了,沒法結束運作;隻看到如下運作結果:
Thread1 持有:objOne 對象鎖,等待擷取:objTwo對象鎖 Thread2 持有:objTwo 對象鎖,等待擷取:objOne對象鎖
這demo沒法結束運作就是由于産生了死鎖,兩個線程都在互相對待擷取對方所持有的對象鎖;
這時候要解決問題就需要找出哪裡出現了死鎖,通過代碼走查通常不容易發現死鎖,當然我們這程式很容易發現,因為我們刻意産生的死鎖;是以就需要工具來檢測死鎖,這裡可用的工具主要有:jconsole、jvisualvm、jstack等,這些工具其實都是jdk自帶的,用法都很類似;
這裡使用jvisualvm來檢測目前的demo程式是否産生了死鎖;打開jvisualvm連接配接到目前的應用程式即可看到程式的監控資訊,如記憶體、CPU、性能、GC等等;打開進入線程的tab項檢視程式的線程資訊,這裡很明顯的就看到了提示該程式被檢測除了死鎖!
點選 線程Dump可以看到線程的堆棧資訊,從中可以看到線程的詳細資訊,并定位死鎖;
從上圖可以看到線程産生死鎖的原因,Thrad2是等待Thread1、Thread1是等待Thread1, 從下圖的堆棧資訊即可定位死鎖産生的位置;
死鎖掃描
除了發現程式出現問題後我們去掃描死鎖外,我們還可以實時的去掃描程式用于發現程式中是否存在死鎖;
JDK提供了MXBean Api可用于掃描程式是否存在死鎖,ThreadMXBean提供了findDeadlockedThreads()方法,可以用于找到産生死鎖的線程;這裡在上面的demo程式中添加一個方法用于掃描死鎖,雖然這種方法可以掃描到死鎖但是由于每次都對線程打快照對程式性能會有比較大的影響,是以慎用;
public static void scanDeadLock() {
ThreadMXBean mxBean = ManagementFactory.getThreadMXBean();
Runnable runnable = () -> {
long[] ids = mxBean.findDeadlockedThreads();
System.out.println("掃描死鎖...");
if (ids != null) {
ThreadInfo[] threadInfos = mxBean.getThreadInfo(ids);
for (ThreadInfo threadInfo : threadInfos) {
System.out.println(threadInfo);
}
}
};
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(5, Executors.defaultThreadFactory());
executorService.scheduleAtFixedRate(runnable, 1, 5, TimeUnit.SECONDS);
}
避免死鎖
解決死鎖最好的方法就是避免死鎖了,比如上面的demo我們可以把直接使用無參數的lock()方法換為使用tryLock方法,tryLock還可以指定擷取鎖逾時時間,到了逾時時間還沒獲得到鎖就會放棄擷取鎖,當然還有其它方法可以避免死鎖;
1、避免使用多個鎖、長時間持有鎖;
2、設計好多個鎖的擷取順序
3、使用帶逾時的擷取鎖方法
文章首發位址:Solinx
http://www.solinx.co/archives/1196