Java併發編程實戰--死鎖

今天看了第十章,描述了經典的“哲學家進餐”問題,Google 哲學家進餐 就有 ,不多解釋。先解釋一下鎖:可以這麼理解,每一個java對象都具有一個鎖標記,而這個鎖標記只能同時分配給一個線程,然後講了死鎖的產生原因:當一個線程永遠地持有一個鎖,而且其他線程都想要取得這個鎖時,那麼它們將永遠被阻塞。下面用三種介紹三種“死鎖”狀況。

一.死鎖

先看代碼

[java] view plaincopy
  1. package 併發編程;  
  2.   
  3. public class LeftRightDeadLock {  
  4.     //注意:容易發生死鎖  
  5.     private final Object left = new Object();  
  6.     private final Object right = new Object();  
  7.   
  8.     public void leftRight(){  
  9.         synchronized(left){  
  10.             synchronized(right){  
  11.                 doSomething();  
  12.             }  
  13.         }  
  14.     }  
  15.   
  16.     public void rightLeft(){  
  17.         synchronized(right){  
  18.             synchronized(left){  
  19.                 doSomethingElse();  
  20.             }  
  21.         }  
  22.     }  
  23.   
  24.     private void doSomethingElse() {  
  25.     }  
  26.       
  27.     private void doSomething() {  
  28.     }  
  29.   
  30. }  

上面的代碼存在死鎖的風險:leftRight()和rightLeft()分別獲得left和right鎖,如果A線程調用leftRight方法,而B線程調用rightLeft方法,而且他們是交錯執行的,如下圖,他們就有會出現死鎖。


出現死鎖的原因是:兩個線程以不同順序來獲取相同的鎖。如果以相同的順序來獲取鎖,就不會出現循環的加鎖依賴性,也就不會出現死鎖。


二.動態的死鎖

看代碼

[java] view plaincopy
  1. package 併發編程;  
  2.   
  3. public class DynamicDeadLock {  
  4.     //注意:容易發生死鎖 T.T  
  5.     public void transferMoney(Account from,Account to,DollarAmount num) throws Exception{  
  6.         synchronized(from){  
  7.             synchronized(to){  
  8.                 if(from.getBalance().compareTo(num) < 0){  
  9.                     throw new Exception();  
  10.                 }  
  11.                 from.debit(num);  
  12.                 to.credit(num);  
  13.             }  
  14.         }  
  15.     }  
  16. }  

上面說了:如果以相同的順序來獲取相同的鎖,就不會出現循環的加鎖依賴性,也就不會出現死鎖。這個類中,只有一個方法,並且也是按照一個順序來獲取鎖,應該不會有什麼問題了吧?但實際上鎖的獲取是動態地取決於線程的行爲,如果同時有A、B兩個線程,A線程調用transferMoney(myBank,hisBank,1000),B線程調用transferMoney(myBank,hisBank,2000),實際上,A線程所謂的myBank其實是B線程的hisBank,A線程的hisBank其實是B線程的myBank,這樣就導致兩個線程獲取鎖的順序不一樣,產生了死鎖。


三.協作對象間的死鎖

先看兩個類

[java] view plaincopy
  1. package 併發編程;  
  2. /** 
  3.  * 出租車類,可被出租車車隊調用 
  4.  * @author Andre 
  5.  * 
  6.  */  
  7. public class Taxi {  
  8.       
  9.     private Point location,destination;//包含兩個屬性:當前位置和目標地  
  10.     private final Dispatcher dispatcher;//所屬車隊  
  11.       
  12.     public Taxi(Dispatcher dispatcher){  
  13.         this.dispatcher = dispatcher;  
  14.     }  
  15.       
  16.     public synchronized Point getLocation(){  
  17.         return location;  
  18.     }  
  19.       
  20.     /** 
  21.      * 通過GPS設置出租車位置 
  22.      * @param location 
  23.      */  
  24.     public synchronized void setLocation(Point location){  
  25.         this.location = location;  
  26.         if(location.equals(destination)){  
  27.             dispatcher.notifyAvailable(this);  
  28.         }  
  29.           
  30.     }  
  31.       
  32. }  

[java] view plaincopy
  1. package 併發編程;  
  2.   
  3. import java.awt.Image;  
  4. import java.util.Set;  
  5.   
  6. /** 
  7.  * 出租車車隊類,指揮出租車的調動 
  8.  * @author Andre 
  9.  * 
  10.  */  
  11. public class Dispatcher {  
  12.     private final Set<Taxi> taxis;//出租車集合  
  13.     private final Set<Taxi> availableTaxis;///可用出租車集合  
  14.       
  15.     public Dispatcher(Set<Taxi> taxis , Set<Taxi> availableTaxis){  
  16.         this.taxis = taxis;  
  17.         this.availableTaxis = availableTaxis;  
  18.     }  
  19.   
  20.     public synchronized  void notifyAvailable(Taxi taxi) {  
  21.         availableTaxis.add(taxi);  
  22.     }  
  23.       
  24.     /** 
  25.      * 獲得包含當前出租車的完整畫面 
  26.      * @return 
  27.      */  
  28.     public synchronized CarImage getImage(){  
  29.         CarImage image = new CarImage();  
  30.         for(Taxi t: taxis)  
  31.             image.drawMarker(t.getLocation());  
  32.         return image;  
  33.     }  
  34.   
  35. }  
這兩個類中,並沒有顯式地獲取兩個鎖,但實際上,現在有一個Dispatcher對象和多個Taxi對象(即一個出租車車隊和多輛出租車),A線程調用Taxi對象中的setLocation時,方法內部調用dispatcher.notifyAvailable(),該方法也加上了同步關鍵字synchronized,這樣A線程就先後獲取了Taxi對象和Dispatcher對象的鎖。而同時B線程調用dispatcher.getImage(該方法有同步標記),而方法內部調用了t.getLocation也是一個同步方法,這樣B線程就先後獲取了Dispatcher對象和Taxi對象的鎖。這樣就回到了上面說過的危險情況:兩個線程試圖用不同順序獲取相同的鎖。而這種情況也是更加難以被程序員發現出來的,因爲它更加隱蔽。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章