【JVM】面試題之死鎖及問題是怎麼定位

在這裏插入圖片描述

前言

之前面試的時候被問到死鎖這塊的問題,藉着最近學習jvm來總結下死鎖相關的知識。如果有地方寫的不到位的地方,麻煩讀者及時提出,放在評論區,我這邊也好及時改正。

回顧

所謂,溫故而知新,首先回顧下,我們之前學過的線程的狀態以及死鎖產生的條件。

線程的狀態

在這裏插入圖片描述
在Java中線程的狀態一共被分成6種:

  • 初始態(NEW)
    • 創建一個Thread對象,但還未調用start()啓動線程時,線程處於初始態。
  • 運行態(RUNNABLE),在Java中,運行態包括 就緒態 和 運行態。
    • 就緒態
      • 該狀態下的線程已經獲得執行所需的所有資源,只要CPU分配執行權就能運 行。 所有就緒態的線程存放在就緒隊列中。
    • 運行態
      • 獲得CPU執行權,正在執行的線程。
      • 由於一個CPU同一時刻只能執行一條線程,因此每個CPU每個時刻只有一條 運行態的線程。
  • 阻塞態(BLOCKED)
    • 當一條正在執行的線程請求某一資源失敗時,就會進入阻塞態。
    • 而在Java中,阻塞態專指請求鎖失敗時進入的狀態。
    • 由一個阻塞隊列存放所有阻塞態的線程。
    • 處於阻塞態的線程會不斷請求資源,一旦請求成功,就會進入就緒隊列,等待執 行。
  • 等待態(WAITING)
    • 當前線程中調用wait、join、park函數時,當前線程就會進入等待態。
    • 也有一個等待隊列存放所有等待態的線程。
    • 線程處於等待態表示它需要等待其他線程的指示才能繼續運行。
    • 進入等待態的線程會釋放CPU執行權,並釋放資源(如:鎖)
  • 超時等待態(TIMED_WAITING)
    • 當運行中的線程調用sleep(time)、wait、join、parkNanos、parkUntil時,就 會進入該狀態;
    • 它和等待態一樣,並不是因爲請求不到資源,而是主動進入,並且進入後需要其 他線程喚醒;
    • 進入該狀態後釋放CPU執行權 和 佔有的資源。
    • 與等待態的區別:到了超時時間後自動進入阻塞隊列,開始競爭鎖。
  • 終止態(TERMINATED)
    • 線程執行結束後的狀態。

死鎖產生的條件

  • 互斥條件:進程對所分配到的資源不允許其他進程進行訪問,若其他進程訪問該資源,只能等待,直至佔有該資源的進程使用完成後釋放該資源
  • 請求和保持條件:進程獲得一定的資源之後,又對其他資源發出請求,但是該資源可能被其他進程佔有,此事請求阻塞,但又對自己獲得的資源保持不放
  • 不可剝奪條件:是指進程已獲得的資源,在未完成使用之前,不可被剝奪,只能在使用完後自己釋放
  • 環路等待條件:是指進程發生死鎖後,必然存在一個進程–資源之間的環形鏈死鎖問題

構造死鎖

編寫代碼,啓動2個線程,Thread1拿到了obj1鎖,準備去拿obj2鎖時,obj2已經被 Thread2鎖定,所以發送了死鎖。

public class TestDeadLock {

    private static Object obj1 = new Object();
    private static Object obj2 = new Object();

    public static void main(String[] args) {
        new Thread(new Thread1()).start();
        new Thread(new Thread2()).start();
    }

    private static class Thread1 implements Runnable {
        @Override
        public void run() {
            synchronized (obj1) {
                System.out.println("Thread1 拿到 obj1 的鎖");
                try {
                    //停頓2秒的意義在於,讓thread2線程拿到obj2的鎖
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (obj2) {
                    System.out.println("Thread1 拿到 obj2 的鎖");
                }
            }

        }
    }

    private static class Thread2 implements Runnable {
        @Override
        public void run() {
            synchronized (obj2) {
                System.out.println("Thread2 拿到 obj2 的鎖");
                try {
                    //停頓2秒的意義在於,讓thread1線程拿到obj1的鎖
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (obj1) {
                    System.out.println("Thread2 拿到 obj1 的鎖");
                }
            }
        }
    }


}

[root@localhost test]# javac TestDeadLock.java 
[root@localhost test]# ll
total 27692
drwxr-xr-x. 9 root root      220 Jan  3 23:22 apache-tomcat-7.0.99
-rw-r--r--. 1 root root  9587605 Dec 11 21:44 apache-tomcat-7.0.99.tar.gz
-rw-------. 1 root root 18737999 Jan  4 16:52 dump.dat
-rw-r--r--. 1 root root      433 Jan  3 23:23 Main.class
-rw-r--r--. 1 root root      135 Jan  3 23:23 Main.java
-rw-r--r--. 1 root root      184 Jan  5 10:08 TestDeadLock$1.class
-rw-r--r--. 1 root root      843 Jan  5 10:08 TestDeadLock.class
-rw-r--r--. 1 root root     1547 Jan  5 10:02 TestDeadLock.java
-rw-r--r--. 1 root root     1066 Jan  5 10:08 TestDeadLock$Thread1.class
-rw-r--r--. 1 root root     1066 Jan  5 10:08 TestDeadLock$Thread2.class
[root@localhost test]# java TestDeadLock
Thread1 拿到 obj1 的鎖
Thread2 拿到 obj2 的鎖
#這裏發生了死鎖,程序一直將等待下去

jstack命令分析

[root@localhost ~]# jstack 13399
2020-01-05 10:09:42
Full thread dump OpenJDK 64-Bit Server VM (25.232-b09 mixed mode):

"Attach Listener" #11 daemon prio=9 os_prio=0 tid=0x00007f5bf0001000 nid=0x3477 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"DestroyJavaVM" #10 prio=5 os_prio=0 tid=0x00007f5c1804b800 nid=0x3458 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Thread-1" #9 prio=5 os_prio=0 tid=0x00007f5c18149000 nid=0x3462 waiting for monitor entry [0x00007f5c1cac7000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at TestDeadLock$Thread2.run(TestDeadLock.java:45)
	- waiting to lock <0x00000000e3466bd0> (a java.lang.Object)
	- locked <0x00000000e3466be0> (a java.lang.Object)
	at java.lang.Thread.run(Thread.java:748)

"Thread-0" #8 prio=5 os_prio=0 tid=0x00007f5c18147000 nid=0x3461 waiting for monitor entry [0x00007f5c1cbc8000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at TestDeadLock$Thread1.run(TestDeadLock.java:25)
	- waiting to lock <0x00000000e3466be0> (a java.lang.Object)
	- locked <0x00000000e3466bd0> (a java.lang.Object)
	at java.lang.Thread.run(Thread.java:748)

"Service Thread" #7 daemon prio=9 os_prio=0 tid=0x00007f5c18117800 nid=0x345f runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007f5c18114800 nid=0x345e waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007f5c18105800 nid=0x345d waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007f5c18103800 nid=0x345c runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007f5c180da000 nid=0x345b in Object.wait() [0x00007f5c1d62a000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x00000000e3408ed8> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
	- locked <0x00000000e3408ed8> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
	at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)

"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007f5c180d5000 nid=0x345a in Object.wait() [0x00007f5c1d72b000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x00000000e3406c00> (a java.lang.ref.Reference$Lock)
	at java.lang.Object.wait(Object.java:502)
	at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
	- locked <0x00000000e3406c00> (a java.lang.ref.Reference$Lock)
	at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"VM Thread" os_prio=0 tid=0x00007f5c180cb800 nid=0x3459 runnable 

"VM Periodic Task Thread" os_prio=0 tid=0x00007f5c1811a000 nid=0x3460 waiting on condition 

JNI global references: 5


Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x00007f5bfc0062c8 (object 0x00000000e3466bd0, a java.lang.Object),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x00007f5bfc004e28 (object 0x00000000e3466be0, a java.lang.Object),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
	at TestDeadLock$Thread2.run(TestDeadLock.java:45)
	- waiting to lock <0x00000000e3466bd0> (a java.lang.Object)
	- locked <0x00000000e3466be0> (a java.lang.Object)
	at java.lang.Thread.run(Thread.java:748)
"Thread-0":
	at TestDeadLock$Thread1.run(TestDeadLock.java:25)
	- waiting to lock <0x00000000e3466be0> (a java.lang.Object)
	- locked <0x00000000e3466bd0> (a java.lang.Object)
	at java.lang.Thread.run(Thread.java:748)

在輸出的信息中,已經看到,發現了1個死鎖,關鍵看這個:

"Thread-1":
	at TestDeadLock$Thread2.run(TestDeadLock.java:45)
	- waiting to lock <0x00000000e3466bd0> (a java.lang.Object)
	- locked <0x00000000e3466be0> (a java.lang.Object)
	at java.lang.Thread.run(Thread.java:748)
"Thread-0":
	at TestDeadLock$Thread1.run(TestDeadLock.java:25)
	- waiting to lock <0x00000000e3466be0> (a java.lang.Object)
	- locked <0x00000000e3466bd0> (a java.lang.Object)
	at java.lang.Thread.run(Thread.java:748)

可以清晰的看到:

  • Thread2獲取了 <0x00000000e3466be0> 的鎖,等待獲取 <0x00000000e3466bd0> 這個鎖
  • Thread1獲取了 <0x00000000e3466bd0> 的鎖,等待獲取 <0x00000000e3466be0> 這個鎖
    由此可見,發生了死鎖。
發佈了97 篇原創文章 · 獲贊 100 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章