Java 中的死鎖

問題:你是怎麼發現死鎖而且是如何預防、如何解決的?java

死鎖的定義:

這裏給出一個我對死鎖的概念的理解:react

多個線程同時被阻塞,而且他們中的一個或多個都在等待某個被佔用資源的釋放。spring

再給出一種百度百科上的解釋,比較全面,便於交叉理解:bash

死鎖是指兩個或兩個以上的進程在執行過程當中,因爲競爭資源或者因爲彼此通訊而形成的一種阻塞的現象,若無外力做用,它們都將沒法推動下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱爲死鎖進程。服務器

死鎖產生的必要條件:

  • 互斥 :一個資源每次只能被一個線程佔用
  • 不可剝奪:進程對正在被其餘進程佔用的資源,不能強行剝奪
  • 請求與保持: 一個進程因請求資源而阻塞時,對已得到的資源保持不放
  • 循環等待:若干進程之間造成一種頭尾相接的循環等待資源關係。

注:只要一個條件不知足,就不會發生死鎖,因此避免死鎖,或者是解決死鎖問題,只須要破壞其中一個必要條件便可。ide

死鎖問題例子:

A 線程要在持有鎖a的前提下嘗試獲取鎖b
工具

B 線程要在持有鎖b的前提下嘗試獲取鎖a
性能

A B 在完成獲取鎖的動做以前,都不會放棄自身持有的鎖,死鎖條件達成,接下來是代碼:spa

class Solution {
  public static void main(String[] args) {
    final Object a = new Object();
    final Object b = new Object();

    Thread threadA =
        new Thread(
            new Runnable() {
              @Override
              public void run() {
                synchronized (a) {
                  System.out.println("threadA in a lock");
                  try {
                    Thread.sleep(1000);
                    synchronized (b) {
                      System.out.println("threadA in b lock");
                    }
                  } catch (InterruptedException e) {
                    // ..
                  }
                }
              }
            });
    Thread threadB =
        new Thread(
            new Runnable() {
              @Override
              public void run() {
                synchronized (b) {
                  System.out.println("threadB in a lock");
                  try {
                    Thread.sleep(1000);
                    synchronized (a) {
                      System.out.println("threadB in b lock");
                    }
                  } catch (InterruptedException e) {
                    // ..
                  }
                }
              }
            });
    threadA.start();
    threadB.start();
  }
}複製代碼

運行結果:

threadA in a lock
命令行

threadB in a lock

產生死鎖,程序明顯停滯了,沒有剩餘的輸出。

死鎖檢測:

這裏咱們可使用JDK提供的兩種檢測工具,進行簡易的死鎖檢測

Jstack 命令:

jstack工具能夠用於生成Java虛擬機當前的快照,是虛擬機中每一條線程正在執行的方法堆棧的集合。

方法:

1 咱們能夠經過 jps獲取當前任務的進程號

E:\react\spring>jps

10256

20404 Launcher

8036 Jps

8188 Solution


2 能夠確認任務的進程號是8188,而後執行jstack查看當前進程的堆棧信息

Found one Java-level deadlock:


"Thread-1":

waiting to lock monitor 0x17677e84 (object 0x073f0dd0, a java.lang.Object), which is held by "Thread-0"

"Thread-0": waiting to lock monitor 0x176798c4 (object 0x073f0dd8, a java.lang.Object),

which is held by "Thread-1"

譯爲:

hread-1這個進程,正在等待一個鎖的釋放,這個monitor的地址是 0x17677e84,它正在被Thread-0這個線程持有

Thread-0這個進程,正在等待一個鎖的釋放,這個monitor的地址是 0x176798c4,它正在被Thread-1這個線程持有。

能夠很明顯的看出,確實存在死鎖。

JConsole工具:

JConsole是JDK自帶的監控工具,在jdk/bin目錄下就能夠找到,它用來鏈接正在運行的本地或遠程JVM,對運行在Java應用程序的資源消耗和性能進行監控,而且會以圖表的形式進行展現。而且本地佔用的服務器內存很小,使用起來很是方便。

方法:

1 在命令行中敲入 jconsole 選擇進程號 8188 進行鏈接

監控圖表:

2 選擇線程選項卡,點擊檢測死鎖

如圖鎖時,使用JConsole的時候,也會顯示相似Jstack的信息,發現死鎖的線程。

如何規避死鎖:

正如以前提到的,避免死鎖,或者說解決死鎖問題,就須要破壞死鎖發生的必要條件。

1 儘可能不要去使用鎖的嵌套,以肯定的順序獲取鎖 ,避免可能產生的循環依賴問題。

修改後的代碼:

class Solution {
  public static void main(String[] args) {
    final Object a = new Object();
    final Object b = new Object();

    Thread threadA =
        new Thread(
            new Runnable() {
              @Override
              public void run() {
                synchronized (a) {
                  System.out.println("threadA in a lock");
                }
                synchronized (b) {
                  System.out.println("threadA in b lock");
                }
              }
            });
    Thread threadB =
        new Thread(
            new Runnable() {
              @Override
              public void run() {
                synchronized (b) {
                  System.out.println("threadB in a lock");
                }
                synchronized (a) {
                  System.out.println("threadB in b lock");
                }
              }
            });
    threadA.start();
    threadB.start();
  }
}
複製代碼

執行結果:

threadA in a lock

threadA in b lock

threadB in a lock

threadB in b lock

2 超時放棄

能夠用ReentrantLock中的 tryLock(long time,TimeUnit unit)方法來方式獲取鎖,該方法會按照固定時長去等待鎖,所以線程能夠在獲取鎖超時以後,主動釋放以前已經釋放的鎖。(內部是一個自旋鎖,不斷的嘗試獲取鎖,超時以後拋出異常)。

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

class Solution {
  public static void main(String[] args) throws InterruptedException {
    ReentrantLock lockA = new ReentrantLock();
    ReentrantLock lockB = new ReentrantLock();
    new Thread(new Runnable() {
      @Override
      public void run() {

        try {
          lockA.lock();
          System.out.println("Thread-2 in Alock");
          Thread.sleep(100);
          lockB.tryLock(1,TimeUnit.SECONDS);
          System.out.println("Thread-2 in Block");
        } catch (InterruptedException e) {
          e.printStackTrace();
        }finally{
          lockB.unlock();
          lockA.lock();
        }
      }
    }).start();
    new Thread(new Runnable() {
      @Override
      public void run() {

        try {
          lockB.lock();
          System.out.println("Thread-1 in Block");
          Thread.sleep(100);
          lockA.lock();
          System.out.println("Thread-1 in Alock");


        } catch (InterruptedException e) {
          e.printStackTrace();
        }finally{
          lockA.unlock();
          lockB.lock();
        }
      }
    }).start();
  }
}複製代碼

執行結果:

Thread-2 in Alock

Thread-1 in Block

Thread-2 in Block

Exception in thread "Thread-0" java.lang.IllegalMonitorStateException at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151) at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261) at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457) at Solution$1.run(Solution.java:22) at java.lang.Thread.run(Thread.java:748)

雖然Thread-0 拋出異常,可是Thread-1中的語句所有執行,並無發生死鎖,程序不會阻塞住。

複製代碼
相關文章
相關標籤/搜索