天天看點

Callable接口、Runable接口、Future接口

1. callable與runable差別

java從釋出的第一個版本開始就可以很友善地編寫多線程的應用程式,并在設計中引入異步處理。thread類、runnable接口和java記憶體管理模型使得多線程程式設計簡單直接。

但thread類和runnable接口都不允許聲明檢查型異常,也不能定義傳回值。沒有傳回值這點稍微有點麻煩。不能聲明抛出檢查型異常則更麻煩一些。

public void run()方法契約意味着你必須捕獲并處理檢查型異常。即使你小心地儲存了異常資訊(在捕獲異常時)以便稍後檢查,但也不能保證這個類(runnable對象)的所有使用者都讀取異常資訊。

你也可以修改runnable實作的getter,讓它們都能抛出任務執行中的異常。但這種方法除了繁瑣也不是十分安全可靠,你不能強迫使用者調用這些方法,程式員很可能會調用join()方法等待線程結束然後就不管了。

但是現在不用擔心了,以上的問題終于在1.5中解決了。callable接口和future接口的引入以及他們對線程池的支援優雅地解決了這兩個問題。

不管用哪種方式建立線程,其本質都是callable接口與runable接口。兩者都是可被其它線程執行的任務!!差別是:

2.future

如上所說,callable任務傳回future對象。即:callable和future一個産生結果,一個拿到結果。

future 表示異步計算的結果。future接口中有如下方法:

    boolean cancel(boolean mayinterruptifrunning)

取消任務的執行。參數指定是否立即中斷任務執行,或者等等任務結束

    boolean iscancelled() 

任務是否已經取消,任務正常完成前将其取消,則傳回 true

    boolean isdone()

任務是否已經完成。需要注意的是如果任務正常終止、異常或取消,都将傳回true

    v get()

等待任務執行結束,然後獲得v類型的結果。interruptedexception 線程被中斷異常, executionexception任務執行異常,如果任務被取消,還會抛出cancellationexception

    v get(long timeout, timeunit unit) 

同上面的get功能一樣,多了設定逾時時間。參數timeout指定逾時時間,uint指定時間的機關,在枚舉類timeunit中有相關的定義。如果計算逾時,将抛出timeoutexception

future接口提供方法來檢測任務是否被執行完,等待任務執行完獲得結果。也可以設定任務執行的逾時時間,這個設定逾時的方法就是實作java程式執行逾時的關鍵。

是以,如果需要設定代碼執行的最長時間,即逾時,可以用java線程池executorservice類配合future接口來實作。

三個簡單的小例子,體會一下:

  

3. future實作類

Callable接口、Runable接口、Future接口

3.1 futuretask

futuretask是一個runnablefuture<v>,而runnablefuture實作了runnbale又實作了futrue<v>這兩個接口,

Callable接口、Runable接口、Future接口
Callable接口、Runable接口、Future接口

另外它還可以包裝runnable和callable<v>

Callable接口、Runable接口、Future接口
Callable接口、Runable接口、Future接口

可以看到,runnable會被executors.callable()函數轉換為callable類型,即futuretask最終都是執行callable類型的任務。該适配函數的實作如下 :

Callable接口、Runable接口、Future接口
Callable接口、Runable接口、Future接口

由于futuretask實作了runnable,是以它既可以通過thread包裝來直接執行,也可以送出給executeservice來執行。見下面兩個例子:

Callable接口、Runable接口、Future接口
Callable接口、Runable接口、Future接口
Callable接口、Runable接口、Future接口
Callable接口、Runable接口、Future接口

如果要執行多個帶傳回值的任務,并取得多個傳回值,兩種方法:

1.先建立一個裝future類型的集合,用executor送出的任務傳回值添加到集合中,最後便利集合取出資料。

這時候,submit的task不一定是按照加入自己維護的list順序完成的。從list中周遊的每個future對象并不一定處于完成狀态,這時調用get()方法就會被阻塞住。

如果系統是設計成每個線程完成後就能根據其結果繼續做後面的事,這樣對于處于list後面的但是先完成的線程就會增加了額外的等待時間。

是以jdk1.8增加了future接口的另外一個實作類completionservice

2.completionservice相當于executor加上blockingqueue,使用場景為當子線程并發了一系列的任務以後,主線程需要實時地取回子線程任務的傳回值并同時順序地處理這些傳回值,誰先傳回就先處理誰。

而completionservice的實作是維護一個儲存future對象的blockingqueue。隻有當這個future對象狀态是結束的時候,才會加入到這個queue中,take()方法其實就是producer-consumer中的consumer。它會從queue中取出future對象,如果queue是空的,就會阻塞在那裡,直到有完成的future對象加入到queue中。

是以,先完成的必定先被取出。這樣就減少了不必要的等待時間。

[ 尐魚兒的qq群:726994578 ] --- [ https://github.com/godmaybelieve ]