天天看點

Future 模式詳解

         該式的核心思想是異步調用。

         在做應用開發時,有時會遇到一種場景:在完成某個主任務的同時,需要處理一些其它的子任務,為了加快響應速度,會将子任務放在另外的線程中執行。例如,搜尋引擎在處理使用者的查詢請求時,不僅要從本地資料庫查找比對的結果,同時可能會向遠端的廣告伺服器請求廣告資料,最後将搜尋結果和廣告一起傳回給使用者。在這個例子中,主線程從本地查找搜尋結果,同時啟動子線程從遠端伺服器擷取廣告資料,當主線程查找結束時,會将子線程得到的廣告資料與搜尋結果合并,最後發送給使用者。主線程啟動子線程後,子線程的執行已不受主線程的控制,其何時執行完畢,主線程無法預知,而其執行結果是由主線程來主動索取的。為了做到這一點,就需要用到Future模式。Future模式是現實中提貨單的抽象,好比去攝影店拍照,照片需要過些時候才能洗出來,而我們不可能一直等下去,商家一般會給我們一張單據,并告知第二天10:00以後憑此單領取照片,而我們就可以暫時離開去做其它事情,等到第二天再帶着單據來到攝影店領取照片,如果我們9:30就到了,照片還沒有洗出來,我們就會繼續等一會兒,直到照片洗出來。

具體的是實作方式有兩種:

1、手動實作

            這種方式不推薦使用。實作的原理是先寫一個線程,然後改線程有兩個邏輯 一個是操作處理資料,另一個是擷取資料,由于用戶端需要處理完的結果,是以我們可以使用wait和notifall來進行兩者間的同步。詳見 http://blog.csdn.net/lmdcszh/article/details/39696357

2、使用JDK提供的并發庫來實作。推薦使用

這裡就以java.util.concurrent.Future 為例簡單說一下Future的具體工作方式。Future對象本身可以看作是一個顯式的引用,一個對異步處理結果的引用。由于其異步性質,在建立之初,它所引用的對象可能還并不可用(比如尚在運算中,網絡傳輸中或等待中)。這時,得到Future的程式流程如果并不急于使用Future所引用的對象,那麼它可以做其它任何想做的事兒,當流程進行到需要Future背後引用的對象時,可能有兩種情況:

  • 希望能看到這個對象可用,并完成一些相關的後續流程。如果實在不可用,也可以進入其它分支流程。
  • “沒有你我的人生就會失去意義,是以就算海枯石爛,我也要等到你。”(當然,如果實在沒有毅力枯等下去,設一個逾時也是可以了解的)

對于前一種情況,可以通過調用Future.isDone()判斷引用的對象是否就緒,并采取不同的處理;而後一種情況則隻需調用get()或

get(long timeout, TimeUnit unit)通過同步阻塞方式等待對象就緒。實際運作期是阻塞還是立即傳回就取決于get()的調用時機和對象就緒的先後了。

簡單而言,Future模式可以在連續流程中滿足資料驅動的并發需求,既獲得了并發執行的性能提升,又不失連續流程的簡潔優雅。

但是Futrue模式有個重大缺陷:當消費者工作得不夠快的時候,它會阻塞住生産者線程,進而可能導緻系統吞吐量的下降。是以不建議在高性能的服務端使用。

java.util.concurrent.Callable與java.util.concurrent.Future類可以協助您完成Future模式。Future模式在請求發生時,會先産生一個Future對象給送出請求的客戶。它的作用類似于代理(Proxy)對象,而同時所代理的真正目标對象的生成是由一個新的線程持續進行。真正的目标對象生成之後,将之設定到Future之中,而當用戶端真正需要目标對象時,目标對象也已經準備好,可以讓客戶提取使用

Callable是一個接口,與Runnable類似,包含一個必須實作的方法,可以啟動為讓另一個線程來執行。不過Callable工作完成後,可以傳回結果對象。

eg:

public class TestFuture {

    /**
     * 處理子任務線程類。
     * 由于需要傳回值是以事先Callable
     * 不需要傳回值可以實作Runnable接口
     */
    static class DoOtherThings implements Callable<Integer>{
        private int i;
        public void setI(int i){
            this.i=i;
        }
        public synchronized int add(){
            try {
                Thread.sleep(4000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return i++;
        }
        @Override
        public Integer call() throws Exception {
            return add();
        }
    }
<span style="font-family:Helvetica,Tahoma,Arial,sans-serif;">   
</span> public static String getsName(String name){
        return name;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException{
        System.out.println("主線程接受用戶端請求...");
        System.out.println("進行主任務執行,于此同時需要處理子任務,子任務耗時");

        /**
         * 由于處理某一個子任務需要的時間比較長,采用Future模式,新開辟一個線程單獨進行該項任務的處理
         * 此時主線程是去執行主線程任務
         */
        ExecutorService exec = Executors.newFixedThreadPool(1);
        DoOtherThings dt = new DoOtherThings();
        dt.setI(10);
        Future<Integer> getValue = exec.submit(dt);
        //用來判斷子線程是否處理完任務
//        if(getValue.isDone()){
//            System.out.println("Done");
//        }
        System.out.println(getsName("做其他事情..Hello Word"));
        int value = 0;
        try {
            //進入阻塞等待狀态,超過5S會逾時不進行等待
            value = getValue.get(5, TimeUnit.SECONDS);
        } catch (TimeoutException e) {
            System.out.println("等待時間太長了,不等了傳回!");
        }
        System.out.println(value);
        exec.shutdown();
    }
}
           

主線程接受用戶端請求...

進行主任務執行,于此同時需要處理子任務,子任務耗時

做其他事情..Hello Word

10