深入理解多線程,死鎖再現


先說說死鎖概念:

     當一個線程永遠地持有一個鎖,並且其他線程都嘗試去獲得這個鎖時,那么它們將永遠被阻塞,這個我們都知道。如果線程A持有鎖L並且想獲得鎖M,線程B持有鎖M並且想獲得鎖L,那么這兩個線程將永遠等待下去,這種情況就是最簡單的死鎖形式。

     在數據庫系統的設計中考慮了監測死鎖以及從死鎖中恢復,數據庫如果監測到了一組事物發生了死鎖時,將選擇一個犧牲者並放棄這個事物。Java虛擬機解決死鎖問題方面並沒有數據庫這么強大,當一組Java線程發生死鎖時,這兩個線程就永遠不能再使用了,並且由於兩個線程分別持有了兩個鎖,那么這兩段同步代碼/代碼塊也無法再運行了----除非終止並重啟應用。

  死鎖是設計的BUG,問題比較隱晦。不過死鎖造成的影響很少會立即顯現出來,一個類可能發生死鎖,並不意味着每次都會發生死鎖,這只是表示有可能。當死鎖出現時,往往是在最糟糕的情況----高負載的情況下

 

看了網上博客的例子,自己實戰了一下:

 

死鎖類(注意這里一定要有"Thread.sleep(2000)"讓線程睡一覺,不然一個線程運行了,另一個線程還沒有運行,先運行的線程很有可能就已經連續獲得兩個鎖了)

 1 package com.xujingyang.DeadLock ;
 2 
 3 public class DeadLock {
 4     
 5     private final Object    left    = new Object() ;
 6     private final Object    right    = new Object() ;
 7     
 8     public void left() throws Exception {
 9         synchronized (left) {
10             Thread.sleep(2000) ;
11             synchronized (right) {
12                 System.out.println("左邊") ;
13             }
14         }
15     }
16     
17     public void right() throws Exception {
18         synchronized (right) {
19             Thread.sleep(2000) ;
20             synchronized (left) {
21                 System.out.println("右邊") ;
22             }
23         }
24     }
25 }

 

多線程執行代理類

 1 package com.xujingyang.DeadLock ;
 2 
 3 public class ProxyLeftLock extends Thread {
 4     
 5     private DeadLock    lock ;
 6     
 7     public ProxyLeftLock(DeadLock lock) {
 8         this.lock = lock ;
 9     }
10     
11     @Override
12     public void run() {
13         try {
14             lock.left() ;
15         } catch (Exception e) {
16             e.printStackTrace() ;
17         }
18     }
19     
20 }

 

 1 package com.xujingyang.DeadLock ;
 2 
 3 public class ProxyRightLock extends Thread {
 4     
 5     private DeadLock    lock ;
 6     
 7     public ProxyRightLock(DeadLock lock) {
 8         this.lock = lock ;
 9     }
10     
11     @Override
12     public void run() {
13         try {
14             lock.right() ;
15         } catch (Exception e) {
16             e.printStackTrace() ;
17         }
18     }
19     
20 }

 

測試類

 1 package com.xujingyang.DeadLock ;
 2 
 3 public class MainTest {
 4     
 5     public static void main(String [] args) {
 6         DeadLock lock = new DeadLock() ;
 7         new ProxyLeftLock(lock).start() ;
 8         new ProxyRightLock(lock).start() ;
 9     }
10 }

 

結果什么也沒打印,因為已經形成了死鎖.

1、jps獲得當前Java虛擬機進程的pid

 

2、jstack打印堆棧。jstack打印內容的最后其實已經報告發現了一個死鎖,但因為我們是分析死鎖產生的原因,而不是直接得到這里有一個死鎖的結論,所以別管它,就看前面的部分

先說明介紹一下每一部分的意思,以"Thread-1"為例:

(1)"Thread-1"表示線程名稱

(2)"prio=6"表示線程優先級

(3)"tid=00000000497cec00"表示線程Id

(4)nid=0x219c

  線程對應的本地線程Id,這個重點說明下。因為Java線程是依附於Java虛擬機中的本地線程來運行的,實際上是本地線程在執行Java線程代碼,只有本地線程才是真正的線程實體。Java代碼中創建一個thread,虛擬機在運行期就會創建一個對應的本地線程,而這個本地線程才是真正的線程實體。Linux環境下可以使用"top -H -p JVM進程Id"來查看JVM進程下的本地線程(也被稱作LWP)信息,注意這個本地線程是用十進制表示的,nid是用16進制表示的,轉換一下就好了

(5)" [0x000000000c9ff000]"表示線程占用的內存地址

(6)"java.lang.Thread.State:BLOCKED"表示線程的狀態

    解釋完了每一部分的意思,看下Thread-1處於BLOCKED狀態,Thread-0處於BLOCKED狀態。對這兩個線程分析一下:

(1)Thread-1獲得了鎖<0x00000007d5d19c60>,在等待鎖<0x00000007d5d19c50>

(2)Thread-0獲得了鎖<0x00000007d5d19c50>,在等待鎖<0x00000007d5d19c60>

  由於兩個線程都在等待獲取對方持有的鎖,所以就這么永久等待下去了。

3、注意一下使用Eclipse/MyEclipse,這段程序如果不點擊控制台上面的紅色方框去Terminate掉它,而是右鍵->Run As->1 Java Application的話,這個進程會一直存在的,這時候可以利用taskkill命令去終止沒有被Terminate的進程:

 

避免死鎖的方式

  既然可能產生死鎖,那么接下來,講一下如何避免死鎖。

1、讓程序每次至多只能獲得一個鎖。當然,在多線程環境下,這種情況通常並不現實

2、設計時考慮清楚鎖的順序,盡量減少嵌在的加鎖交互數量

3、既然死鎖的產生是兩個線程無限等待對方持有的鎖,那么只要等待時間有個上限不就好了。當然synchronized不具備這個功能,但是我們可以使用Lock類中的tryLock方法去嘗試獲取鎖,這個方法可以指定一個超時時限,在等待超過該時限之后變回返回一個失敗信息

 

       參考博客:http://www.cnblogs.com/xrq730/p/4853713.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM