天天看點

Java中死鎖的定位與修複

死鎖應該可以說是并發程式設計中比較常見的一種情況,可以說如果程式産生了死鎖那将會對程式帶來緻命的影響;是以排查定位、修複死鎖至關重要;

我們都知道死鎖是由于多個對象或多個線程之間互相需要 對方鎖持有的鎖而又沒有釋放對方所持有的鎖,導緻雙方都永久處于阻塞狀态 ;

如上圖所示,線程1持有對象1的鎖、線程2持有對象2的鎖,持此線程1又想去擷取對象2對象鎖、線程2想擷取對象1對象鎖,此時由于雙方都沒有擷取到想要的鎖,任務沒完成是以也沒釋放鎖,導緻一直僵持呢,于是阻塞、産生死鎖;

死鎖檢測

需要檢測死鎖肯定要先有死鎖出現,下面的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();
}
 }                 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);
}                 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、使用帶逾時的擷取鎖方法

如果對自己未來有想法,想提升自己,你現在在JAVA這條路上掙紮,也想在IT行業拿高薪,可以參加我們免費的公開課試聽學習 幹貨滿滿的,選擇最适合自己的課程學習,技術大牛親授,課程内容有:Java工程化、高性能及分布式、高性能、深入淺出。高架構。性能調優、Spring,MyBatis,Netty源碼分析和大資料等多個知識點。如果你想拿高薪的,想學習的,想就業前景好的,想跟别人競争能取得優勢的,想進阿裡面試但擔心面試不過的,你都可以來。

群号:468947140

進群修改群備注:開發年限-地區-經驗

點選連結加入群聊【Java-BATJ企業級資深架構】:https://jq.qq.com/?_wv=1027&k=52j2FVO