深入理解java中多線程Future模式

先上一個場景:假如你突然想做飯,但是沒有廚具,也沒有食材。網上購買廚具比較方便,食材去超市買更放心。

實現分析:在快遞員送廚具的期間,我們肯定不會閒著,可以去超市買食材。所以,在主線程裡面另起一個子線程去網購廚具。但是,子線程執行的結果是要返回廚具的,而run方法是沒有返回值的。所以,這才是難點,需要好好考慮一下。

模擬代碼1:

深入理解java中多線程Future模式

運行結果:

深入理解java中多線程Future模式

可以看到,多線程已經失去了意義。在廚具送到期間,我們不能幹任何事。對應代碼,就是調用join方法阻塞主線程。

有人問了,不阻塞主線程行不行???

不行!!!

從代碼來看的話,run方法不執行完,屬性chuju就沒有被賦值,還是null。換句話說,沒有廚具,怎麼做飯。

Java現在的多線程機制,核心方法run是沒有返回值的;如果要保存run方法裡面的計算結果,必須等待run方法計算完,無論計算過程多麼耗時。

面對這種尷尬的處境,程序員就會想:在子線程run方法計算的期間,能不能在主線程裡面繼續異步執行???

這種想法的核心就是Future模式,下面先應用一下Java自己實現的Future模式。

模擬代碼2:

深入理解java中多線程Future模式

運行結果:

深入理解java中多線程Future模式

可以看見,在快遞員送廚具的期間,我們沒有閒著,可以去買食材;而且我們知道廚具到沒到,甚至可以在廚具沒到的時候,取消訂單不要了。

好神奇,有沒有。

下面具體分析一下第二段代碼:

1)把耗時的網購廚具邏輯,封裝到了一個Callable的call方法裡面。

深入理解java中多線程Future模式

Callable接口可以看作是Runnable接口的補充,call方法帶有返回值,並且可以拋出異常。

2)把Callable實例當作參數,生成一個FutureTask的對象,然後把這個對象當作一個Runnable,作為參數另起線程。

深入理解java中多線程Future模式

這個繼承體系中的核心接口是Future。Future的核心思想是:一個方法f,計算過程可能非常耗時,等待f返回,顯然不明智。可以在調用f的時候,立馬返回一個Future,可以通過Future這個數據結構去控制方法f的計算過程。

這裡的控制包括:

get方法:獲取計算結果(如果還沒計算完,也是必須等待的)

cancel方法:還沒計算完,可以取消計算過程

isDone方法:判斷是否計算完

isCancelled方法:判斷計算是否被取消

這些接口的設計很完美,FutureTask的實現註定不會簡單,後面再說。

深入理解java中多線程Future模式

3)在第三步裡面,調用了isDone方法查看狀態,然後直接調用task.get方法獲取廚具,不過這時還沒送到,所以還是會等待3秒。對比第一段代碼的執行結果,這裡我們節省了2秒。這是因為在快遞員送貨期間,我們去超市購買食材,這兩件事在同一時間段內異步執行。

通過以上3步,我們就完成了對Java原生Future模式最基本的應用。下面具體分析下FutureTask的實現,既然FutureTask也是一個Runnable,那就看看它的run方法

深入理解java中多線程Future模式

其實 run 方法作用非常簡單,就是調用 callable 的 call 方法返回結果值 result,根據是否發生異常,調用 set(result)或 setException(ex)方法表示 FutureTask 任務完結。不過因為 FutureTask 任務都是在多線程環境中使用,所以要注意併發衝突問題。注意在 run方法中,我們沒有使用 synchronized 代碼塊或者 Lock 來解決併發問題,而是使用了 CAS 這個樂觀鎖來實現併發安全,保證只有一個線程能運行 FutureTask 任務。

這裡表示狀態的屬性state是個什麼鬼

深入理解java中多線程Future模式

把FutureTask看作一個Future,那麼它的作用就是控制Callable的call方法的執行過程,在執行的過程中自然會有狀態的轉換:

1)一個FutureTask新建出來,state就是NEW狀態;COMPETING和INTERRUPTING用的進行時,表示瞬時狀態,存在時間極短;NORMAL代表順利完成;EXCEPTIONAL代表執行過程出現異常;CANCELED代表執行過程被取消;INTERRUPTED被中斷

2)執行過程順利完成:NEW -> COMPLETING -> NORMAL

3)執行過程出現異常:NEW -> COMPLETING -> EXCEPTIONAL

4)執行過程被取消:NEW -> CANCELLED

5)執行過程中,線程中斷:NEW -> INTERRUPTING -> INTERRUPTED

get 方法

get 方法就是阻塞獲取線程執行結果,這裡主要做了兩個事情

1. 判斷當前的狀態,如果狀態小於等於 COMPLETING,表示 FutureTask 任務還沒有完結,所以調用 awaitDone 方法,讓當前線程等待。

2. report 返回結果值或者拋出異常

深入理解java中多線程Future模式

awaitDone

如果當前的結果還沒有被執行完,把當前線程線程和插入到等待隊列

深入理解java中多線程Future模式

report

report 方法就是根據傳入的狀態值 s,來決定是拋出異常,還是返回結果值。這個兩種情況都表示 FutureTask 完結了

深入理解java中多線程Future模式

總結:

FutureTask 是 Runnable 和 Future 的結合,如果我們把 Runnable 比作是生產者,Future 比作是消費者,那麼 FutureTask 是被這兩者共享的,生產者運行 run 方法計算結果,消費者通過 get 方法獲取結果。作為生產者消費者模式,有一個很重要的機制,就是如果生產者數據還沒準備的時候,消費者會被阻塞。當生產者數據準備好了以後會喚醒消費者繼續執行。


分享到:


相關文章: