當多條線程以不同的順序搶佔同步資源的時候,就有可能發生死鎖。
如下圖所示,線程1持有鎖對象A而希望獲得鎖對象B;另一方面,線程2持有鎖對象B而希望
獲得鎖對象A。並且這兩個線程的操作是交錯執行的,因此它們會發生死鎖。
當發生的死鎖後,JDK自帶了兩個工具(jstack和JConsole),可以用來監測分析死鎖的發生原因。
jstack工具用於生於生成虛擬機當前時刻的線程快照。線程快照就是當前虛擬機每一條線程正在
執行的方法堆棧的集合,生成快照可以用於定位諸如線程死鎖、死循環等問題。
JConsole是一種可視化監視管理工具。用於連接正在運行的JVM進程,以監控Java 應用程序性
能和跟蹤 Java 中的代碼。
下面以一個死鎖例子來說明如何使用這兩個工具來分析線程死鎖。
死鎖示例代碼如下:
- class SynThread implements Runnable{
- int a ,b;
- public SynThread(int a,int b){
- this.a=a;
- this.b=b;
- }
- @Override
- public void run() {
- synchronized (Integer.valueOf(a)) {//必須用valueOf()方法
- synchronized (Integer.valueOf(b)) {
- System.err.println("a+b=="+(a+b));
- }
- }
- }
- }
- public class entry {
- public static void main(String[] args) {
- //循環主要是爲了加大死鎖概率
- for(int i=0;i<100;i++){
- new Thread(new SynThread(1,2)).start();
- new Thread(new SynThread(2,1)).start();
- }
- }
- }
說明:以上代碼有可能發生死鎖,原因是Integer.valueOf()方法作了緩存優化,對[-128,127]之間
的數字會被緩存。也就是說,循環代碼中一共只創建了兩個不同的對象。假設在兩個synchronized
塊之間發生了線程切換,那就有可能造成,線程A等待被線程B持有Integer.valueOf(1)對象,線程B
等待被線程A持有Integer.valueOf(2)對象,結果出現了死鎖。(可能需要多次執行直到程序出現
阻塞現象)
JDK源代碼Integer.java類對valueOf()方法的優化細節如下
- public static Integer valueOf(int i) {
- if(i >= -128 && i <= IntegerCache.high)
- return IntegerCache.cache[i + 128];
- else
- return new Integer(i);
- }
- 使用jstack工具導出線程堆棧信息(以Windows環境爲例)
1.找到運行當前程序的JVM的進程id,命令及結果如下
2.運行jstack命令,並將結果信息導出來
3.使用文本編輯器打開剛導出的文本,只要查看最後關於死鎖的堆棧信息即可
4.直接從堆棧信息不能直觀得到結論,沒關係,我們可以畫圖理清線程間的調度情況
(出現閉合環路,發生死鎖)
- 使用JConsole可視化工具檢測死鎖
1.直接執行JConsole工具(在jdk/bin目錄下),選擇目標JVM進程,然後點擊連接
2.切換到“線程”標籤頁,點擊“檢測死鎖”按鈕
3.選擇不同的線程,可以查看其資源調度信息,採用類似的方法分析,可以得出與
採用jstack分析一樣的結論