2010年10月4日 星期一

[ Java設計模式 ] 多線程設計模式 : Future Pattern (先給你這張提貨單)


前言 :
我去蛋糕店蛋糕, 下訂單後, 店員請我天黑之後來店取貨, 並給我一張提貨單. 黃昏後, 我就拿著這張提貨單到蛋糕店取貨. 店員拿著提貨單並將我要的蛋糕交給我. 這章要學習的是Future Pattern. 假設有一個執行需要一點時間的方法, 我們就不要等待執行結果出來, 而是獲取一張替代的”提貨單”. 因為領取提貨單並不需要時間, 這時這個提貨單就是 Future 參予者.
在繼續本章前, 請務必先看過 Thread-Per Message Pattern.

Future Pattern 所有參予者 :
Client (委託人) 參予者 : 
Client 參予者會向 Host 參予者送出請求 (request), 並且 Client 參予者會馬上得到 VitrualData 參予者作為這個請求的結果. 然而這個VirtualData 參予者, 實際上是 Future 參予者. 也就是Client 參予者並不需要知道返回值是RealData 參予者還是Future 參予者. 一段時間後, Client 會透過 VirtualData參予者進行操作. 範例程序中 Client 參予者是 Main 類.

Host 參予者 : 
Host 參予者會建立出新的線程, 開始建立 RealData 參予者. 另一方面會對 Client 參予者 (以VirtualData參予者形式) 返回 Future參予者. 範例程序中, Host 參予者是 Host 類.

VirtualData 參予者 :
VirtualData 參予者是用來統一代表 Future 參予者 與 RealData 參予者. 範例程序中 VirtualData 參予者是 Data 接口.

RealData 參予者 :
RealData 參予者用來表示實際的數據, 建立這個對象需要一點時間. 範例程序中. RealData 參予者是 RealData 類.

Future 參予者 :
Future 參予者是 Host 參予者傳送給 Client 參予者, 當作是 RealData參予者的 “提貨單” 使用的參予者.範例程序中. Future 參予者是 FutureData 類.


範例程式 :
Future Pattern UML 類圖 :


* Main 類 :
Main 類會調用 Host 的 request 方法來獲取 Data, 之後接著呼叫Data 的 getContent 來顯示 Data 內容. 代碼如下 :
  1. package dp.thread.ch9;  
  2.   
  3. public class Main {  
  4.     public static void main(String args[]) {  
  5.         System.out.println("Main Begin");  
  6.         Host host = new Host();  
  7.         IData data1 = host.request(10'A');  
  8.         IData data2 = host.request(20'B');  
  9.         IData data3 = host.request(30'C');  
  10.   
  11.         System.out.println("Main otherJob Begin");  
  12.         try{  
  13.             Thread.sleep(2000);  
  14.         }catch(InterruptedException e){};  
  15.         System.out.println("Main otherJob END");  
  16.         System.out.println("data1="+data1.getContent());  
  17.         System.out.println("data2="+data2.getContent());  
  18.         System.out.println("data3="+data3.getContent());  
  19.         System.out.println("Main END");;  
  20.     }  
  21. }  
* Host 類 :
Host 類會提供request 方法來產出 Data. 一開始會先建立 FutureData 的物件並回傳. 而後接著開啟線程進行 RealData 的製作並將之傳入 FutureData中. 代碼如下 :
  1. package dp.thread.ch9;  
  2.   
  3. public class Host {  
  4.     public IData request(final int count, final char c) {  
  5.         System.out.println("    Request("+count+","+c+")BEGIN");  
  6.         //(1) 建立 FutureData 物件.  
  7.         final FutureData future = new FutureData();  
  8.         //(2) 建立 RealData 物件後, 啟動新線程  
  9.         new Thread(){  
  10.             public void run() {  
  11.                 RealData realData = new RealData(count,c);  
  12.                 future.setRealData(realData);  
  13.             }  
  14.         }.start();  
  15.         System.out.println("    Request("+count+","+c+")END");  
  16.         //(3) 返回FutureData 物件.  
  17.         return future;  
  18.     }  
  19. }  
* IData 接口 :
IData 接口是用來表現數據的訪問方式的接口. FutureData 與 RealData 都實現這個接口. 代碼如下 :
  1. package dp.thread.ch9;  
  2.   
  3. public interface IData {  
  4.     public abstract String getContent();  
  5. }  
* FutureData 類 :
FutureData類在Future Pattern 代表 "提貨單". 代碼如下 :
  1. package dp.thread.ch9;  
  2.   
  3. public class FutureData implements IData{  
  4.     private RealData realData = null;  
  5.     private boolean ready = false;  
  6.   
  7.     public synchronized void setRealData(RealData realData) {  
  8.         if(ready) {  
  9.             return//balk  
  10.         }  
  11.         this.realData = realData;  
  12.         this.ready = true;  
  13.         notifyAll();  
  14.     }  
  15.   
  16.     public synchronized String getContent() {  
  17.         while(!ready) {  
  18.             try{  
  19.                 wait();  
  20.             }catch(InterruptedException e){}  
  21.   
  22.         }  
  23.         return realData.getContent();  
  24.     }      
  25. }  
* RealData :
就是前言介紹的"蛋糕", 在製造過程中需要花費時間等待. 代碼如下 :
  1. package dp.thread.ch9;  
  2.   
  3. public class RealData implements IData{  
  4.     private final String content;  
  5.       
  6.     public RealData(int count, char c) {  
  7.         System.out.println("    Making ReadData("+count+","+c+")BEGIN");  
  8.         char[] buffer = new char[count];  
  9.         for(int i=0;i
  10.          buffer[i] = c;  
  11.          try{  
  12.              Thread.sleep(100);  
  13.          }catch(InterruptedException e){}  
  14.         }  
  15.         System.out.println("    Making RealData"+count+","+c+")END");  
  16.         this.content = new String(buffer);  
  17.     }  
  18.   
  19.     public String getContent() {  
  20.         return content;  
  21.     }     
  22. }  


執行結果 :
Main Begin
Request(10,A)BEGIN
Request(10,A)END
Request(20,B)BEGIN
Request(20,B)END
Request(30,C)BEGIN
Request(30,C)END
Main otherJob Begin
Making ReadData(10,A)BEGIN
Making ReadData(30,C)BEGIN
Making ReadData(20,B)BEGIN
Making RealData10,A)END
Main otherJob END <需要等到RealData ready 才能取得到 Data content>
data1=AAAAAAAAAA
Making RealData20,B)END
data2=BBBBBBBBBBBBBBBBBBBB
Making RealData30,C)END
data3=CCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
Main END


補充說明 :
@. 能夠提升throughput 嗎 :
在單CPU 上運行 Java 並把單一連串運算以多線程來處理, 應該是不會提高 throughput. 因為就算是由多線程分擔運算, 最後還是由一個CPU 在負責運算. 但是如果加上 I/O 處理, 就不一樣了. 例如對硬盤訪問時, 並不是所有工作都是由CPU進行的.當對硬盤讀寫時, CPU 只是在等待工作結束而已. 對CPU 而言這是”空閒時間”. 若能將這些空閒時間分配給其他線程繼續處理其他操作, 是可能提高throughput的.

@. 變形 – 不讓人等待的 Future參予者 :
範例程序中, 當調用 FutureData 的 getContent方法時, 如果RealData 實例還沒建好, 會使用 Guarded Suspension Pattern , 等到建立好為止. 因為會等待到建立好為止, 所以從 FutureData 的 getContent 方法退出時, 一定會得到需要的訊息. 可是 getContent 方法實際上也可以設計成異步的. 考慮使用 Balking Pattern , “如果還沒好, 就馬上退出”. 既然 RealData 物件還沒建立好, 就先回去稍微作一下自己的工作, 之後再調用一次 getContent 試試看. 這樣可以達到不用等待RealData 的 Future Pattern.
This message was edited 3 times. Last update was at 13/01/2010 11:57:53

2 則留言:

[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...