多線程環境下,死鎖即兩個或兩個以上的線程去爭奪同一個共享資源,而導致互相等待的情況。
要產生死鎖,必須滿足如下四個條件:
- 互斥條件,共享資源x和y只能被一個線程占有
- 請求和保持條件,T1持有x,并請求y,但不釋放x
- 不可搶占條件,T1持有x,即使T2需要x,也不能強行從T1那里奪取x。資源不能被強行從持有它的線程中奪取,只有線程自愿釋放它所持有的資源,其他線程才能獲得這些資源。
- 循環等待條件,線程T1等到線程T2占用的資源,線程T2等到線程T1占用的資源,循環等待。
死鎖已經產生,只能通過外部的干預來解決,比如重啟或kill了線程。
出現死鎖后,可以通過jstack命令去導出線程的dump日志,然后從dump日志中定位到具體的死鎖程序代碼。
實例:
- 使用jps找到java的pid
jps
12345 MyJavaApp
67890 Jps
- 使用jstack將輸出重定向到文件
jstack 12345 > stack_trace.txt
- 使用cat查看輸出文件
cat stack_trace.txt"main" #1 prio=5 os_prio=0 tid=0x00007f8c5400b800 nid=0x1b03 waiting on condition [0x00007f8c5d1eb000]java.lang.Thread.State: TIMED_WAITING (sleeping)at java.lang.Thread.sleep(Native Method)at MyJavaApp.main(MyJavaApp.java:15)"Worker-1" #2 prio=5 os_prio=0 tid=0x00007f8c5400c800 nid=0x1b04 runnable [0x00007f8c5d3eb000]java.lang.Thread.State: RUNNABLEat MyJavaApp.doWork(MyJavaApp.java:30)at MyJavaApp.run(MyJavaApp.java:25)at java.lang.Thread.run(Thread.java:748)
- 分析上面的stack_trace.txt
jstack 的輸出會列出每個線程的堆棧信息,包括線程 ID、線程狀態、鎖信息等。示例如下:
"main" #1 prio=5 os_prio=0 tid=0x00007f8c5400b800 nid=0x1b03 waiting on condition [0x00007f8c5d1eb000]java.lang.Thread.State: TIMED_WAITING (sleeping)at java.lang.Thread.sleep(Native Method)at MyJavaApp.main(MyJavaApp.java:15)"Worker-1" #2 prio=5 os_prio=0 tid=0x00007f8c5400c800 nid=0x1b04 runnable [0x00007f8c5d3eb000]java.lang.Thread.State: RUNNABLEat MyJavaApp.doWork(MyJavaApp.java:30)at MyJavaApp.run(MyJavaApp.java:25)at java.lang.Thread.run(Thread.java:748)
“main” 和 “Worker-1”:線程的名稱。
#1 和 #2:Java 線程 ID,這個 ID 是由 Java 虛擬機分配的,通常是一個遞增的長整型值。Java 里的線程 ID 可以通過 Thread.getId() 獲取,并與 jstack 輸出中的 # 后的數字對應。
tid=0x00007f8c5400b800 和 tid=0x00007f8c5400c800:線程對象的內存地址。
nid=0x1b03 和 nid=0x1b04:本地線程 ID(Native Thread ID),由操作系統分配。jstack 輸出中的 nid 字段即為本地線程 ID。它是用于操作系統級的線程管理和調度。nid,可以與操作系統級線程工具輸出的 LWP 對應。
例如:
終端上使用命令
ps -L -p <pid>輸出:
PID LWP TTY TIME CMD
12345 12345 pts/0 00:00:00 java
12345 12346 pts/0 00:00:00 java
12345 12347 pts/0 00:00:00 java
LWP 是 Light Weight Process ID,對應于本地線程 ID(十進制形式)。要將 nid 與 LWP 對應起來,可以將十六進制的 nid 轉換為十進制。例如,nid=0x1b03 轉換為十進制是 6915。
也可以通過破壞死鎖產生的四個條件:
- 互斥條件是鎖本身的特征,無法被破壞
- 線程第一次執行時,一次性申請所有的共享資源來破壞請求和保持條件
- 占用部分共享資源的同時,在進一步申請其他資源的時候,如果申請不到就主動釋放它已有的資源。破壞不可搶占條件。雖然看起來這種策略也破壞了請求和保持條件,但實際上,它是通過破壞不可搶占條件來間接打破請求和保持條件的。關鍵點在于,資源的釋放行為不再僅僅由持有線程的意愿決定,而是受到資源分配策略的影響,這直接破壞了不可搶占條件。
- 給共享資源編號,線程按順序去申請資源就可以破壞循環等待條件。