【Java併發編程】之九:死鎖(含代碼)

當線程需要同時持有多個鎖時,有可能產生死鎖。考慮如下情形:

      線程A當前持有互斥所鎖lock1,線程B當前持有互斥鎖lock2。接下來,當線程A仍然持有lock1時,它試圖獲取lock2,因爲線程B正持有lock2,因此線程A會阻塞等待線程B對lock2的釋放。如果此時線程B在持有lock2的時候,也在試圖獲取lock1,因爲線程A正持有lock1,因此線程B會阻塞等待A對lock1的釋放。二者都在等待對方所持有鎖的釋放,而二者卻又都沒釋放自己所持有的鎖,這時二者便會一直阻塞下去。這種情形稱爲死鎖。

      下面給出一個兩個線程間產生死鎖的示例,如下:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public class Deadlock extends Object {  
  2.     private String objID;  
  3.   
  4.     public Deadlock(String id) {  
  5.         objID = id;  
  6.     }  
  7.   
  8.     public synchronized void checkOther(Deadlock other) {  
  9.         print("entering checkOther()");  
  10.         try { Thread.sleep(2000); }   
  11.         catch ( InterruptedException x ) { }  
  12.         print("in checkOther() - about to " + "invoke 'other.action()'");  
  13.   
  14.         //調用other對象的action方法,由於該方法是同步方法,因此會試圖獲取other對象的對象鎖  
  15.         other.action();  
  16.         print("leaving checkOther()");  
  17.     }  
  18.   
  19.     public synchronized void action() {  
  20.         print("entering action()");  
  21.         try { Thread.sleep(500); }   
  22.         catch ( InterruptedException x ) { }  
  23.         print("leaving action()");  
  24.     }  
  25.   
  26.     public void print(String msg) {  
  27.         threadPrint("objID=" + objID + " - " + msg);  
  28.     }  
  29.   
  30.     public static void threadPrint(String msg) {  
  31.         String threadName = Thread.currentThread().getName();  
  32.         System.out.println(threadName + ": " + msg);  
  33.     }  
  34.   
  35.     public static void main(String[] args) {  
  36.         final Deadlock obj1 = new Deadlock("obj1");  
  37.         final Deadlock obj2 = new Deadlock("obj2");  
  38.   
  39.         Runnable runA = new Runnable() {  
  40.                 public void run() {  
  41.                     obj1.checkOther(obj2);  
  42.                 }  
  43.             };  
  44.   
  45.         Thread threadA = new Thread(runA, "threadA");  
  46.         threadA.start();  
  47.   
  48.         try { Thread.sleep(200); }   
  49.         catch ( InterruptedException x ) { }  
  50.   
  51.         Runnable runB = new Runnable() {  
  52.                 public void run() {  
  53.                     obj2.checkOther(obj1);  
  54.                 }  
  55.             };  
  56.   
  57.         Thread threadB = new Thread(runB, "threadB");  
  58.         threadB.start();  
  59.   
  60.         try { Thread.sleep(5000); }   
  61.         catch ( InterruptedException x ) { }  
  62.   
  63.         threadPrint("finished sleeping");  
  64.   
  65.         threadPrint("about to interrupt() threadA");  
  66.         threadA.interrupt();  
  67.   
  68.         try { Thread.sleep(1000); }   
  69.         catch ( InterruptedException x ) { }  
  70.   
  71.         threadPrint("about to interrupt() threadB");  
  72.         threadB.interrupt();  
  73.   
  74.         try { Thread.sleep(1000); }   
  75.         catch ( InterruptedException x ) { }  
  76.   
  77.         threadPrint("did that break the deadlock?");  
  78.     }  
  79. }  

     運行結果如下:


     從結果中可以看出,在執行到other.action()時,由於兩個線程都在試圖獲取對方的鎖,但對方都沒有釋放自己的鎖,因而便產生了死鎖,在主線程中試圖中斷兩個線程,但都無果。


     大部分代碼並不容易產生死鎖,死鎖可能在代碼中隱藏相當長的時間,等待不常見的條件地發生,但即使是很小的概率,一旦發生,便可能造成毀滅性的破壞。避免死鎖是一件困難的事,遵循以下原則有助於規避死鎖: 

     1、只在必要的最短時間內持有鎖,考慮使用同步語句塊代替整個同步方法;

     2、儘量編寫不在同一時刻需要持有多個鎖的代碼,如果不可避免,則確保線程持有第二個鎖的時間儘量短暫;

     3、創建和使用一個大鎖來代替若干小鎖,並把這個鎖用於互斥,而不是用作單個對象的對象級別鎖;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章