並行設計模式(一)-- Future模式


  Java多線程編程中,常用的多線程設計模式包括:Future模式、Master-Worker模式、Guarded Suspeionsion模式、不變模式和生產者-消費者模式等。這篇文章主要講述Future模式,關於其他多線程設計模式的地址如下:
  關於Master-Worker模式的詳解: 並行設計模式(二)-- Master-Worker模式
  關於Guarded Suspeionsion模式的詳解: 並行設計模式(三)-- Guarded Suspeionsion模式
  關於不變模式的詳解: 並行設計模式(四)-- 不變模式
  關於生產者-消費者模式的詳解:並行設計模式(五)-- 生產者-消費者模式

1. Future模式

  Future模式的核心在於:去除了主函數的等待時間,並使得原本需要等待的時間段可以用於處理其他業務邏輯。

  Future模式有點類似於商品訂單。在網上購物時,提交訂單后,在收貨的這段時間里無需一直在家里等候,可以先干別的事情。類推到程序設計中時,當提交請求時,期望得到答復時,如果這個答復可能很慢。傳統的是一直持續等待直到這個答復收到之后再去做別的事情,但如果利用Future模式,其調用方式改為異步,而原先等待返回的時間段,在主調用函數中,則可以用於處理其他事務。

  例如如下的請求調用過程時序圖。當call請求發出時,需要很長的時間才能返回。左邊的圖需要一直等待,等返回數據后才能繼續其他操作;而右邊的Future模式的圖中客戶端則無需等到可以做其他的事情。服務器段接收到請求后立即返回結果給客戶端,這個結果並不是真實的結果(是虛擬的結果),也就是先獲得一個假數據,然后執行其他操作。

 

  Future模式的主要參與者如下表所示:

   參 與 者  

                     作  用                      
 Main  系統啟動,調用Client發出請求
 Client  返回Data對象,立即返回FutureData,並開啟ClientThread線程裝配RealData
 Data    返回數據的接口
 FutureData  Future數據,構造很快,但是是一個虛擬的數據,需要裝配RealData
 RealData  真實數據,其構造是比較慢的

2. Future模式的代碼實現

應用實例對應模式結構圖如下所示:

    

<1. Main函數的實現

  Main函數主要負責調用Client發起請求,並使用返回的數據:

public class Main { public static void main(String[] args) { Client client = new Client(); // 這里會立即返回,因為獲取的是FutureData,而非RealData Data data = client.request("name"); System.out.println("請求完畢"); try { // 這里可以用一個sleep代替對其他業務邏輯的處理 // 在處理這些業務邏輯過程中,RealData也正在創建,從而充分了利用等待時間 Thread.sleep(2000); // 使用真實數據 System.out.println("數據=" + data.getResult()); } catch (InterruptedException e) { e.printStackTrace(); } } }

<2. Client的實現

  Client主要實現了獲取futrueData,開啟構造RealData的線程,並在接受請求后,很快地返回FutureData

public class Client { public Data request(final String string) { final FutureData futureData = new FutureData(); new Thread(new Runnable() { @Override public void run() { // RealData的構建很慢,所以放在單獨的線程中運行 RealData realData = new RealData(string); futureData.setRealData(realData); } }).start(); return futureData; // 先直接返回FutureData  } }

<3. Data的實現

  Data是一個接口,提供了getResult()方法。無論futureData或者RealData都實現了這個接口

public interface Data { String getResult() throws InterruptedException; }

<4. FutureData的實現

  FutureData實現了一個快速返回的RealData包裝。它只是一個包裝,或者說是一個RealData的虛擬實現。因此,它可以很快被構造並返回。當使用FutureData的getResult()方法是,程序會阻塞,等待RealData被注入到程序中,才使用RealData的getResult()方法返回。

 1 public class FutureData implements Data {  2 RealData realData = null; // FutureData是RealData的封裝  3 boolean isReady = false; // 是否已經准備好  4  5 public synchronized void setRealData(RealData realData) {  6 if (isReady)  7 return;  8 this.realData = realData;  9 isReady = true; 10 notifyAll(); // RealData已經被注入到FutureData中了,通知getResult()方法 11  } 12 13  @Override 14 public String getResult() throws InterruptedException { 15 if (!isReady) { 16 wait(); // 一直等到RealData注入到FutureData中 17  } 18 return realData.getResult(); 19  } 20 }

<5. RealData的實現

  RealData是最終需要使用的數據模型,它的構造很慢。在這里,使用sleep()函數模擬這個過程

public class RealData implements Data { protected String data; public RealData(String data) { // 利用sleep方法來表示RealData構造過程是非常緩慢的 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } this.data = data; } @Override public String getResult() throws InterruptedException { return data; } }

3. JDK的內置Future模式實現

  由於Future是非常常用的多線程設計模式,因此在JDK中內置了Future模式的實現。這些類在java.util.concurrent包里面。其中最為重要的是FutureTask類,它實現了Runnable接口,作為單獨的線程運行。在其run()方法中,通過Sync內部類調用Callable接口,並維護Callable接口的返回對象。當使用FutureTask.get()方法時,將返回Callable接口的返回對象。其核心結構圖如下所示:

    

  JDK內置的Future模式功能強大,除了基本的功能外,它還可以取消Future任務,或者設定future任務的超時時間。Callable接口是一個用戶自定義的實現。在應用程序中,通過實現Callable接口的call()方法,指定FutureTask的實際工作內容和返回對象。

  Future接口提供的線程控制功能有:

1 boolean cancle(boolean mayInterruptIfRunning); // 取消任務 2 boolean isCancelled();  // 是否已經取消 3 boolean isDone();    // 是否已經完成 4 V get() throws InterruptedException, ExecutionException;  //取得返回對象 5 V get(long timeout, TimeUnit unit);  //取得返回對象,可以設置超時時間

  同樣,針對上述的實例,如果使用JDK自帶的實現,則需要作一些調整。

  首先,需要實現Callable接口,實現具體的業務邏輯。在本例中,依然使用RealData來實現這個接口:

 1 public class RealData implements Callable<String> {  2 private String para;  3  4 public RealData(String para) {  5 this.para = para;  6  }  7  8  @Override  9 public String call() throws Exception { 10 // 利用sleep方法來表示真是業務是非常緩慢的 11 StringBuffer sb = new StringBuffer(); 12 for (int i = 0; i < 10; i++) { 13  sb.append(para); 14 try { 15 Thread.sleep(1000); 16 } catch (InterruptedException e) { 17  e.printStackTrace(); 18  } 19  } 20 return sb.toString(); 21  } 22 }

  在這個改進中,RealData的構造變動非常快,因為其主要業務邏輯被移動到call()方法內,並通過call()方法返回。

  Main方法修改如下,由於使用了JDK的內置框架,Data、FutureData等對象就不再需要了。在Main方法的實現中,直接通過RealData構造FutureTask,並將其作為單獨的線程運行。在提交請求后,執行其他業務邏輯,最后通過FutureTask.get()方法,得到RealData的執行結果。

 1 public class Main {  2 public static void main(String[] args) {  3 FutureTask<String> future = new FutureTask<String>(new RealData("liangyongxing"));  4 ExecutorService executor = Executors.newFixedThreadPool(1); // 使用線程池  5 //執行FutureTask,相當於上例中的client.request("name")發送請求  6 //在這里開啟線程進行RealData的call()執行  7  executor.submit(future);  8 System.out.println("請求完畢");  9 10 try { 11 // 這里仍然可以做額外的數據操作,這里使用sleep代替其他業務邏輯的處理 12 Thread.sleep(2000); 13 14 /** 15  * 相當於上例當中的 data.getResult(),取得call()方法的返回值 16  * 如果此時call()方法沒有執行完畢,則依然會等待 17 */ 18 System.out.println("數據 = " + future.get()); 19 } catch (InterruptedException | ExecutionException e) { 20  e.printStackTrace(); 21  } finally {
              executor.shutdown();
          } 22 } 23 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM