新手淺談Future


     Future到底是什么東西?很多人都對這個東西感到特別奇怪(好吧,我承認,那個很多人就只是我自己而已),就我現在的理解,因為本人在並發這方面沒有多少實踐經驗,所以只好就着一些資料和自己的理解給它下個定義,Future就是保存我們任務的完成信息,比如說,任務中會通過返回某些東西告訴別人它已經結束了,而Future中就保存了這種信息。利用Futu保存和得到任務的結果的用法如下: 

      Future<String> future = threadPool.submit(new Callable<String>(){
           @Override
           public String call() throws Exception{
                Thread.sleep(3000);
                return "future";
           }
        try{
             System.out.println("waiting.....");
             System.out.println(future.get());
        }catch(InterruptedException e){
              e.printStackTrace();
        }catch(ExecutionException e){
              e.printStackTrace();
        }

          注意到沒?代碼中的System.out.println(future.get())這一句,就是返回任務的結果,而任務的結果是保存在Future<String>中的,相信大家都有注意到,        

     Future<String> future = threadPool.submit(new Callable<String>(){
       @Override
        public String call() throws Exception{}
       }

 

         Callable相當於Runnable,所以,這里實現的是一個線程,但是與Runnable不同的是,它是具有返回值的,這個返回值就是我們想要任務返回的結果,比如說,我們想要任務返回的是一個提示信息,那么,返回值可以是String,然后在我們要實現的call()方法中return一句提示信息,接着只要使用Future類的get()方法,就可以從里面得到提示信息了,只要任務完成。所以,由此我們可以知道,java SE5比起以前來,在並發這方面做了更多的工作,它完善了我們的並發線程機制,使我們可以更好的根據任務的完成情況來進行與其他任務的協作,比如說,我們可以通過Future的返回值來決定是否終止任務,或者開啟另一個任務。任務的終止可以使用Future的方法future.cancel(boolean),其中boolean為true或false,來決定是否終止,至於開啟另一個任務,可以重新開啟另一個線程,但是這里就馬上有個問題浮現出來,就是當Futrue的結果返回來時,該任務有沒有結束呢?因為這時一定已經執行完該任務的call()方法。是的,該任務已經結束了,只是我們沒有取出它的返回結果而已。

        看到上面,相信大家一定都對Future的新特性產生非常濃厚的興趣,非常想要將這個新玩意兒馬上運用起來,但是,且慢,每次在遇到這種新東西的時候,我們都會有一個念頭,那就是我們有必要使用嗎?如果舊的東西已經足夠用了,那為什么還要用多余的方法呢?是的,這種想法是對的,因為我們的程序設計原則都是能夠盡量簡單則盡量簡單,但是Future是一個接口,一個泛型接口,適合各種返回值的情況,而且這個接口提供了很多有用的方法,再加上,我們永遠無法知道我們的代碼以后到底會變成怎么樣子,是否需要添加新的功能等等,而這些,如果一開始使用的是舊的東西的話,添加新的東西,那么,我們就要對我們的代碼進行修改,但是,我是這么認為的,就目前而言,Thread修改為Future並不是很難,所以,這方面倒是沒有多大顧慮,熟悉啥就用啥,至少都要了解,因為我們在寫代碼時,更多時間里是在閱讀別人的代碼,如果別人使用的代碼是使用以前的接口的話,而且這種情況是非常常見的,所以,我們必須要看得懂代碼並且能夠將其轉化為我們的新接口,這些就需要我們能夠對其有一定的了解,並且明白它們之間的聯系和區別。所以,接下來就是介紹一下新接口的一些方法以便我們能夠更好的使用新接口。

    A、boolean cancel(Boolean mayInterruptlfRunning):試圖取消該Future里關聯的Callable任務
    B、<?> get()  throws InterruptedException, ExecutionException :返回Callable任務里的call方法的返回值,調用該方法將導致線程阻塞,必須等到子線程結束才得到返回值,但是並不會妨礙其他任務的執行。線程被中斷會拋出InterruptedException異常,任務執行中出現錯誤會拋出ExecutionException異常,如果任務被取消,還會拋出CancellationException 異常。
    C、<?> get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException :返回Callable任務里的call方法的返回值,該方法讓程序最多阻塞timeout和unit指定的時間,如果經過指定時間后Callable任務依然沒有返回值,將會拋出TimeoutException,其他與上面的get()方法用法一樣。
    D、boolean isCancelled():如果在Callable任務正常完成前被取消,則返回true。
    E、boolean isDone():如果Callable任務已經完成,則返回true,而且這里要注意,如果任務正常終止、異常或取消,那么都將會返回true.
    關於Future的使用,其實重點並不在於我們對它的方法到底有多么熟悉,因為我們完全可以通過查找文檔來了解它們的用法,那么我們需要掌握的是什么呢?就是通過利用Future,我們可以做到什么?它解決的主要問題到底是什么?這些都是有利於我們對它的掌握。很多人都喜歡研究接口的內部實現,是的,這個很重要,因為想要了解接口的實現,知道它的實現細節是非常重要的,只要知道它的實現細節,那么我上面的問題其實也就可以馬上得到答案。但是我並不是什么大師級人物,所以像是這種枯燥的東西完全沒有信心可以講得通俗易懂,自己沒有暈都不錯了!所以,就只能通過一些用例及其代碼來研究研究,如果是非要知道底細的讀者,還請自己詳細閱讀一下相關文檔。
       那么,進入正題,既然我們知道Future是一個接口,那么,就一定有一個具體的類的實現,那么這個具體的類是什么呢?就是FutureTask.FutureTask的作用非常大,甚至可以說是使用Future的靈魂,因為就是它用來包裝Callable對象的,所以,很多方法都是通過它來實現的。
      代碼如:
      
ExecutorService executor = Executors.newSingleThreadExecutor();   
FutureTask<String> future =   
       new FutureTask<String>(new Callable<String>() {//使用Callable接口作為構造參數   
         public String call() {   
           //真正的任務在這里執行,這里的返回值類型為String,可以為任意類型   
       }});   
executor.execute(future);   
//在這里可以做別的任何事情   
try {   
    result = future.get(5000, TimeUnit.MILLISECONDS); //取得結果,同時設置超時執行時間為5秒。同樣可以用future.get(),不設置執行超時時間取得結果   
} catch (InterruptedException e) {   
    futureTask.cancel(true);   
} catch (ExecutionException e) {   
    futureTask.cancel(true);   
} catch (TimeoutException e) {   
    futureTask.cancel(true);   
} finally {   
    executor.shutdown();   
}  

      這里就是FutureTask的一般用法,它最大的好處就是我們可以將任務交給執行器后執行其他操作,然后再從里面得到任務的結果。這里必須要注意,只有FutureTask這種既實現Runnable又實現Callable才能夠通過executor()遞交給ExecutorService,而Future不行,只能通過submit(),因為executor()要求的參數是一個實現了Runnable的類。如果我們不想要直接構造Future對象,那么我們可以這樣寫:

      

ExecutorService executor = Executors.newSingleThreadExecutor();   
FutureTask<String> future = executor.submit(   
   new Callable<String>() {//使用Callable接口作為構造參數   
       public String call() {   
      //真正的任務在這里執行,這里的返回值類型為String,可以為任意類型   
   }});   
//在這里可以做別的任何事情   
//同上面取得結果的代碼  

       這里是使用ExecutorService.submit方法來獲得Future對象,submit方法既支持Callable接口類型,也支持Runnable接口作為參數,具有很大的靈活性,而且所有的submit()方法都會返回一個Future值,無論是Runnable還是Callable。上面兩種方法哪種比較好?其實都一樣,只是第二種的話,可以在定義FutureTask的同時就將FutureTask提交給Executor。個人的話,比較傾向於第二種,因為我們的代碼如果在不影響閱讀性的基礎上能夠越簡單越好,哪怕是一句代碼。

       Future中的call()方法相比run()方法更加強大,除了上面說的可以具有返回值外,相信大家在上面的代碼中也可以看到,call()方法是可以聲明異常的,這樣,就能省去run()方法的異常處理。
   創建並啟動有返回值的線程的步驟如下:
   一、創建Callable接口的實現類,並實現call方法,該call方法的返回值,並作為線程的執行體。
   二、創建Callable實現類的實例,使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call方法的返回值
   三、使用FutureTask對象作為Thread對象的target創建、並啟動新線程
   四、調用FutureTask對象的方法來獲得子線程執行結束后的返回值
    FutureTask還實現了Runnable接口,所以可以直接交給Executor執行,正如上面的代碼所演示的。這種好處就非常明顯,因為我們還是可以將我們的線程交給Executor來管理,不用學習新的線程管理機制。我們都知道,Executor框架的實現基本上是用Runnable,但是Runnable的能力還是相當有限,它不能返回一個值,也不能拋出異常,更重要的是,對於復雜費時的計算根本難以處理。為什么這么說呢?那是因為Runnable並沒有方法可以支持任務超時。什么是任務超時?就是我們允許這個任務的執行要多久后才返回結果,如果這個任務在指定的時間內沒有完成,那么就會拋出異常,那就是可以設置超時的get()方法。但是我們知道,Runnable中並沒有與get()類似的方法,我們最多能做的,只是讓線程睡眠。
      至此我們就可以知道,Future大概是怎樣的東西。其實,它的名字含義就已經說明一切,Future,未來的意思,說明我們通過Future是為了應付未來的問題。什么是未來的問題,這個就是它所解決的問題,超時問題,以前,我嫩得到任務的結果是即時性的,但是現在可以有一個時間上的緩沖,可以在這段等待的時間內執行其他的動作,這樣我們的並發設計就能更加靈活,而且還能更好的應付現實生活中的實際問題,因為很多問題都是不能馬上就能解決並且返回結果的。這就是Future的長處,異步處理,但是Future又不同於一般的異步處理機制,它也可以選擇同步,所以靈活性更大。這點怎么做到的呢?取決於它里面的方法。比如說,我們可以根據流程的需要決定是否需要等待(Future.isDone()),何時等待(Future.get()),等待多久(Future.get(timeout)),還可以根據它的返回值判斷數據是否就緒而決定要不要借這個空檔完成其它任務。 
       現實生活中的並發問題非常多而且要求又不盡相同,所以,自然,Future也有很多相應的衍生形態。下面就只是介紹幾種常見的,因為能力有限,加上這方面的工作又幾乎沒有,所以,如果是想要更加詳細的內容,還請查閱相關資料。
       1.Lazy Future
       與一般的Future不同,Lazy Future在創建之初不會主動開始准備引用的對象,而是等到請求對象時才開始相應的工作。因此,Lazy Future本身並不是為了實現並發,而是以節約不必要的運算資源為出發點,效果上與Lambda/Closure類似。例如設計某些API時,你可能需要返回一組信息,而其中某些信息的計算可能會耗費可觀的資源。但調用者不一定都關心所有的這些信息,因此將那些需要耗費較多資源的對象以Lazy Future的形式提供,可以在調用者不需要用到特定的信息時節省資源。
另外Lazy Future也可以用於避免過早的獲取或鎖定資源而產生的不必要的互斥,因為並沒有准備對象。
       2.Promise
       Promise可以看作是Future的一個特殊分支,常見的Future一般是由服務調用者直接觸發異步處理流程,比如調用服務時立即觸發處理或 Lazy Future的取值時觸發處理。但Promise則用於顯式表示那些異步流程並不直接由服務調用者觸發的情景。例如Future接口的定時控制,其異步流程不是由調用者,而是由系統時鍾觸發,再比如淘寶的分布式訂閱框架提供的Future式訂閱接口,其等待數據的可用性不是由訂閱者決定,而在於發布者何時發布或更新數據。因此,相對於標准的Future,Promise接口一般會多出一個set()或fulfill()接口。
        3.復用式的Future
        常規的Future是一次性的,也就是說當你獲得了異步的處理結果后,Future對象本身就失去意義了。但經過特殊設計的Future也可以實現復用,這對於可多次變更的數據顯得非常有用。例如前面提到的淘寶分布式訂閱框架所提供的Future式接口,它允許多次調用waitNext()方法(相當於Future.get()),每次調用時是否阻塞取決於在上次調用后是否又有數據發布,如果尚無更新,則阻塞直到下一次的數據發布。這樣設計的好處是,接口的使用者可以在其任何合適的時機,或者直接簡單的在獨立的線程中通過一個無限循環響應訂閱數據的變化,同時還可兼顧其它定時任務,甚至同時等待多個Future。簡化的例子如下:

      

for (;;) {  
  schedule = getNextScheduledTaskTime();  
  while(schedule > now()) {  
    try {  
      data = subscription.waitNext(schedule - now());  
      processData(data);  
    } catch(Exception e) {...}  
  }  
  doScheduledTask();  
}  

 

 


免責聲明!

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



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