程式扎記: [ Java小學堂 ] 多線程設計模式 : 線程的共享互斥與synchronized 介紹

標籤

2010年8月27日 星期五

[ Java小學堂 ] 多線程設計模式 : 線程的共享互斥與synchronized 介紹

前言 : 
在多線程環境裡, 多個線程可以自由操作, 當然就能對同一時例進行存取. 這個情況有時候會造成許多無法預期的結果與不必要的問題. 假設現在是要從銀行帳戶取錢, 確認可用餘額這部分的程序代碼應為 : 

  1. if ( 可用餘額大於提領金額 ) {  
  2.     進行提領動作.  
  3. }  

先確認可用餘額再允許是否可以提領. 如果可以提領則可以從可用餘額扣除提領金額. 這樣才不會發生可用餘額變成負數的問題. 
但是舉例來說, 假設 : 
可用餘額 = 1000 元 
預提領金額 = 1000 元 
線程A | 線程B 
"可用餘額大於提領金額?" | 
->是 | 
<<<<這裡切換成線程B>>>> 
| "可用餘額大於提領金額?" 
| -> 是 
| "從可用餘額減掉預提領金額" 
| -> 可用餘額變成 0 
<<<<這裡切換成線程A>>>> 
"從可用餘額減掉預提領金額" | 
-> 可用餘額變成 0 | 

當線程A 和 B同時操作, 由於時間差, 可能發生線程B 夾在 線程A 的 "確認可用餘額" 和 " 減去可用餘額 " 這兩個動作間發生錯誤的預期行為. 這時候需要有一種類似 "交通管制" 來協助防止發生這類的問題. 例如當某個線程正在執行某個部份時, 則限制其他線程不可以執行此部分. 這種像是交通管制的操作就稱為共享互斥或互斥控制, 英文寫作 mutual exclusion. Java 語言在處理線程的共享互斥會使用到關鍵字 synchronized. 

synchronized 方法 : 
當一個方法加上關鍵字synchronized 聲明後, 就可以讓一個線程操作這個方法. " 讓一個線程操作 "並不是說只能讓一個特定線程操作而已, 而是指同一時間只能讓一個線程執行或存取. 這種現程稱為 synchronized 方法, 又稱同步方法. 以如下Bank類別為範例, API : deposit 與 API : withdraw 這兩個方法都是 synchronized方法 : 

  1. public class Bank{  
  2.   private int money;  
  3.   private String name;  
  4.   
  5.   public Bank(String n,int m){  
  6.     this.name = n;  
  7.     this.money = m;  
  8.   }  
  9.   
  10.   // 存款  
  11.   public synchronized void deposit(int m){  
  12.     money+=m;  
  13.   }  
  14.     
  15.   // 取款  
  16.   public synchronized boolean withdraw(int m){  
  17.     if (money >= m) {  
  18.       money -= m;  
  19.       return true;  // 已取款  
  20.     } else {  
  21.       return false;  // 餘額不足  
  22.     }  
  23.   }  
  24.     
  25.   public String getName(){  
  26.     return name;  
  27.   }  
  28. }  

Bank使用說明 : 

* 當一個線程在執行Bank 實例的deposit 方法時, 其他線程就不能執行同一個實例的 deposit 方法或 withdraw 方法, 預執行的線程必須排隊等候. 如下圖 :

* Bank 裡還有一個 getName方法. 他不是 synchronized 方法, 所以無論其它線程是否正在執行 deposit 或 withdraw, getName 的方法隨時可以執行.
* 當執行synchronized 方法的線程執行完畢該方法後, 鎖定即被釋放並由其他等候線程爭取下一次的鎖定.如下圖 :


* 只要有實例就有一個鎖定, 因此說不是因為每個實例的synchronized方法在執行, 導致其他實例的synchronized 方法無法執行. 如下圖:


synchronized 阻擋 : 
如果只想在方法裡某一部分的, 而非整個方法阻擋多線程讀取, 則可以使用 synchronized 阻擋, 其格式如下 : 

  1. synchronized (表達式) {  
  2.     同步的部分;  
  3. }  
這樣即可把鎖定的實例傳給 " 表達式 " 這部分. 如欲更精密控制共享互斥的範圍, 則可以使用 synchronized 阻擋. 

* synchronized 實例方法 與 synchronized 阻擋 
假設現在有一個類型如下的synchronized 實例方法 : 

  1. synchronized void method(){  
  2. ...  
  3. }  
在功能上, 它跟以下的 synchronized 阻擋時等效的. 

  1. void method(){  
  2.   synchronized (this) {  
  3.     ...  
  4.   }  
  5. }  
換句話說, synchronized 實例方法是使用 this 鎖定去做線程的共享互斥. 

* synchronized 類方法 與 synchronized 阻擋 
假設現在有一個類型如下的synchronized 類方法 : 

  1. class Something {  
  2.   static synchronized void method(){  
  3.     ...  
  4.   }  
  5. }  
在功能上就等同於如下的 synchronized 阻擋 : 

  1. class Something {  
  2.   static void method(){  
  3.     synchronized (Something.class){  
  4.         ...  
  5.     }  
  6.   }  
  7. }  
換句話說, synchronized 類方法等同是使用該類的類對象進行鎖定的動作進行共享互斥. Something.class 是對應 Something類的 java.lang.Class類的實例. 

補充資料 : 
@. [Java] Synchronized 心得

2 則留言:

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!