原帖地址:http://eyesmore.javaeye.com/blog/243648

Future 模式(異步調用)

在多線程交互的中2,經常有一個線程需要得到另個一線程的計算結果,我們常用的是Future異步模式來加以解決。
Future顧名思意,有點像期貨市場的“期權”,是“對未來的一種憑證”,例如當我們買了某個房地產開發商的期房,交錢之后,開發商會給我們一個憑證 (期權),這個憑證告訴我們等明年某個時候拿這個憑證就可以拿到我們所需要的房子,但是現在房子還沒建好。市場上之所以有“期貨”,也正由于有這種需求, 才有這種供給。

這種應用在GUI上用的比較多,在設計模式中一般稱為“虛擬代理模式”。

例如:現在有個這樣的需求,Client向Server提交一個Request(int count,char c),希望獲取一個由count個字符c構造出來的字符串。比如發送Request(10,'K'),那么反饋字符串“KKKKKKKKKK”,但是我們 假設這個生成字符串的過程很費時間。

于是,為了獲取比較好的交互性,我們的Server收到請求后,先構造一個FutureData,并把這個所謂的“期權(未來憑證)”反饋給 Client;于此同時,通過另一個并發線程去構造一個真正的字符串RealData,并在構造完畢后,RealData給FutureData報告一個 消息,說數據(期房)已經準備好了,此時Client可以通過期權拿到期房,但是假如我們的Client比較著急,還沒等房子假好的時,就想要房子,怎么 辦呢?這個時候我們可以阻塞Client所在的線程,讓Client等待,直到最后RealData通知FutureData說房子好了,才返回。

這里的要點:

(1)Server先給Client一個“期 權”,同時開一個線程去干活建房子(未來的“現房”);

(2)當“現房”RealData準備好了的 時候,如何告訴FutureData說已經準備好了。(本處采用“回調過程”(借用觀察者模式,來實現回調))

(3)如果客戶比較著急,現房還沒準備好的時 候,就要取房,怎么辦?  本處采用“阻塞”。

Data(公共數據接口)

Java代碼
  1. package com.umpay.future;  
  2.   
  3. public interface Data {  
  4.     public abstract String getContent();  
  5. }  

FutureData(期權)

Java代碼
  1. package com.umpay.future.extend;  
  2.   
  3. import java.util.Observable;  
  4. import java.util.Observer;  
  5.   
  6. import com.umpay.future.Data;  
  7.   
  8. public class FutureData2 implements Data,Observer {  
  9.   
  10.     /**  
  11.      * 存 放真實數據,并且標志真正的數據是否已經準備完畢 
  12.      * 被多線程享受 
  13.      * 如果realData2==null,表示數據還準備好 
  14.      * */  
  15.     private volatile RealData2 realData2 = null;  
  16.     /** 
  17.      * 查 看真正的數據是否準備完畢 
  18.      * */  
  19.     public boolean isFinished() {  
  20.         return realData2 != null;  
  21.     }  
  22.       
  23.     /** 
  24.      * 如 果數據已經準備好,則返回真正的數據; 
  25.      * 否 則,阻塞調用線程,直到數據準備完畢后,才返回真實數據; 
  26.      * */  
  27.     public String getContent() {  
  28.         synchronized (mutex) {  
  29.             while(!isFinished()) {//只要數據沒有準備完畢,就阻塞調用線程  
  30.                 try {  
  31.                     mutex.wait();  
  32.                 } catch (InterruptedException e) {  
  33.                     e.printStackTrace();  
  34.                 }  
  35.             }  
  36.             return realData2.getContent();  
  37.         }  
  38.     }  
  39.   
  40.     /** 
  41.      *  當 RealData2 準 備完數據后,RealData2 應該通知 FutureData2 數據準備完畢。 
  42.      *  并在輸入參數 realData 傳入真實數據,在參數 event 傳入事件(比如數據如期準備好 了,或出了什么異常) 
  43.      * 
  44.      *  @param  realData    真實的數據 
  45.      *  @param  event       事件類型 
  46.      * */  
  47.     public void update(Observable realData, Object event) {  
  48.         System.out.println("通知...."+event);  
  49.         if(!(realData instanceof RealData2)) {  
  50.             throw new IllegalArgumentException("主題的數據類型必須是RealData2");  
  51.         }  
  52.         if(!(event instanceof String)) {  
  53.             throw new IllegalArgumentException("事件的數據類型必須是String");  
  54.         }  
  55.         synchronized (mutex) {  
  56.             if(isFinished()) {  
  57.                 mutex.notifyAll();  
  58.                 return;//如果數據 已經準備好了,直接返回.  
  59.             }  
  60.             if("Finished".equals(event)) {  
  61.                 realData2 = (RealData2)realData;//數據準備好了的時候,便可以通知數據準備好了  
  62.                 mutex.notifyAll();//喚醒被阻塞的線程  
  63.             }   
  64.         }  
  65.     }  
  66.   
  67.     private Object mutex = new Object();  
  68. }  

RealData(實際數據)

Java代碼
  1. package com.umpay.future.extend;  
  2.   
  3. import java.util.Observable;  
  4.   
  5. import com.umpay.future.Data;  
  6.   
  7. public class RealData2 extends Observable implements Data {  
  8.   
  9.     private String content;  
  10.   
  11.     public RealData2() {  
  12.           
  13.     }  
  14.       
  15.     public void createRealData2(int count, char c) {  
  16.         System.out.println("        making RealData(" + count + ", " + c  
  17.                 + ") BEGIN");  
  18.         char[] buffer = new char[count];  
  19.         for (int i = 0; i < count; i++) {  
  20.             buffer[i] = c;  
  21.             try {  
  22.                 Thread.sleep(100);  
  23.             } catch (InterruptedException e) {  
  24.             }  
  25.         }  
  26.         System.out.println("        making RealData(" + count + ", " + c  
  27.                 + ") END");  
  28.         this.content = new String(buffer);  
  29.           
  30.         //真實數據準備完畢了,通知FutureData2說數據已經準備好了.  
  31.         setChanged();//必須先設置本對象的狀態發生了變化,并且通知所有的觀察者  
  32.         notifyObservers("Finished");  
  33.     }  
  34.       
  35.   
  36.     public String getContent() {  
  37.         return content;  
  38.     }  
  39. }  

服務端代碼:

Java代碼
  1. package com.umpay.future.extend;  
  2.   
  3. import com.umpay.future.Data;  
  4.   
  5. public class HostServer2 {  
  6.   
  7.     public Data request(final int count, final char c) {  
  8.         System.out.println("    request(" + count + ", " + c + ") BEGIN");  
  9.   
  10.         // (1) 建立FutureData的實體  
  11.         final FutureData2 future2 = new FutureData2();  
  12.   
  13.         // (2) 為了建立RealData的實體,啟動新的線程  
  14.         new Thread() {  
  15.             public void run() {  
  16.                 RealData2 realdata2 = new RealData2();  
  17.                 realdata2.addObserver(future2);//以便當RealData2把數據準備完畢后,通過該回調口子,通知FutureData2表示數據已經貯備好了  
  18.                 realdata2.createRealData2(count, c);  
  19.             }  
  20.         }.start();  
  21.   
  22.         System.out.println("    request(" + count + ", " + c + ") END");  
  23.   
  24.         // (3) 取回FutureData實體,作為傳回值  
  25.         return future2;  
  26.     }  
  27.   
  28. }  

客戶端代碼:

Java代碼
  1. package com.umpay.future;  
  2.   
  3. import com.umpay.future.extend.HostServer2;  
  4.   
  5. public class MainClient {  
  6.     public static void main(String[] args) {  
  7. //      testHostServer();  
  8.         testHostServer2();  
  9.     }  
  10.       
  11.     static void testHostServer() {  
  12.         System.out.println("main BEGIN");  
  13.         HostServer hostServer = new HostServer();  
  14.         Data data1 = hostServer.request(10, 'A');  
  15.         Data data2 = hostServer.request(20, 'B');  
  16.         Data data3 = hostServer.request(30, 'C');  
  17.   
  18.         System.out.println("main otherJob BEGIN");  
  19. //        try {  
  20. //            Thread.sleep(2000);  
  21. //        } catch (InterruptedException e) {  
  22. //        }  
  23.         System.out.println("main otherJob END");  
  24.   
  25.         System.out.println("data1 = " + data1.getContent());  
  26.         System.out.println("data2 = " + data2.getContent());  
  27.         System.out.println("data3 = " + data3.getContent());  
  28.         System.out.println("main END");  
  29.   
  30.     }  
  31.   
  32.     static void testHostServer2() {  
  33.         System.out.println("main BEGIN");  
  34.         HostServer2 hostServer2 = new HostServer2();  
  35.         Data data1 = hostServer2.request(10, 'A');  
  36.         Data data2 = hostServer2.request(20, 'B');  
  37.         Data data3 = hostServer2.request(30, 'C');  
  38.   
  39.         System.out.println("main otherJob BEGIN");  
  40. //        try {  
  41. //            Thread.sleep(2000);  
  42. //        } catch (InterruptedException e) {  
  43. //        }  
  44.         System.out.println("main otherJob END");  
  45.   
  46.         System.out.println("data1 = " + data1.getContent());  
  47.         System.out.println("data2 = " + data2.getContent());  
  48.         System.out.println("data3 = " + data3.getContent());  
  49.         System.out.println("main END");  
  50.   
  51.     }  
  52. }