- 相關(guān)推薦
簡單講解Java的Future編程模式方案
用過Java并發(fā)包的朋友或許對Future (interface) 已經(jīng)比較熟悉了,其實Future 本身是一種被廣泛運用的并發(fā)設(shè)計模式,可在很大程度上簡化需要數(shù)據(jù)流同步的并發(fā)應(yīng)用開發(fā)。在一些領(lǐng)域語言(如Alice ML )中甚至直接于語法層面支持Future。
這里就以java.util.concurrent.Future 為例簡單說一下Future的具體工作方式。Future對象本身可以看作是一個顯式的引用,一個對異步處理結(jié)果的引用。由于其異步性質(zhì),在創(chuàng)建之初,它所引用的對象可能還并不可用(比如尚在運算中,網(wǎng)絡(luò)傳輸中或等待中)。這時,得到Future的程序流程如果并不急于使用Future所引用的對象,那么它可以做其它任何想做的事兒,當流程進行到需要Future背后引用的對象時,可能有兩種情況:
希望能看到這個對象可用,并完成一些相關(guān)的后續(xù)流程。如果實在不可用,也可以進入其它分支流程。
“沒有你我的人生就會失去意義,所以就算海枯石爛,我也要等到你!保ó斎唬绻麑嵲跊]有毅力枯等下去,設(shè)一個超時也是可以理解的)
對于前一種情況,可以通過調(diào)用Future.isDone()判斷引用的對象是否就緒,并采取不同的處理;而后一種情況則只需調(diào)用get()或
get(long timeout, TimeUnit unit)通過同步阻塞方式等待對象就緒。實際運行期是阻塞還是立即返回就取決于get()的調(diào)用時機和對象就緒的先后了。
簡單而言,F(xiàn)uture模式可以在連續(xù)流程中滿足數(shù)據(jù)驅(qū)動的并發(fā)需求,既獲得了并發(fā)執(zhí)行的性能提升,又不失連續(xù)流程的簡潔優(yōu)雅。
與其它并發(fā)設(shè)計模式的對比
除了Future外,其它比較常見的并發(fā)設(shè)計模式還包括“回調(diào)驅(qū)動(多線程環(huán)境下)”、“消息/事件驅(qū)動(Actor模型中)”等。
回調(diào)是最常見的異步并發(fā)模式,它有即時性高、接口設(shè)計簡單等有點。但相對于Future,其缺點也非常明顯。首先,多線程環(huán)境下的回調(diào)一般是在觸發(fā)回調(diào)的模塊線程中執(zhí)行的,這就意味著編寫回調(diào)方法時通常必須考慮線程互斥問題;其次,回調(diào)方式接口的提供者在本模塊的線程中執(zhí)行用戶應(yīng)用的回調(diào)也是相對不安全的,因為你無法確定它會花費多長時間或出現(xiàn)什么異常,從而可能間接導(dǎo)致本模塊的即時性和可靠性受影響;再者,使用回調(diào)接口不利于順序流程的開發(fā),因為回調(diào)方法的執(zhí)行是孤立的,要與正常流程匯合是比較困難的。因此回調(diào)接口適合于在回調(diào)中只需要完成簡單任務(wù),并且不必與其它流程匯合的場景。
上述這些回調(diào)模式的缺點恰恰正是Future的長項。由于Future的使用是將異步的數(shù)據(jù)驅(qū)動天然的融入順序流程中,因此你完全不必考慮線程互斥問題,F(xiàn)uture甚至可以在單線程的程序模型(例如協(xié)程)中實現(xiàn)(參見下文將要提到的“Lazy Future”)。另一方面,提供Future接口的模塊完全不必擔心像回調(diào)接口那樣的可靠性問題和可能對本模塊的即時性影響。
另一類常見的并發(fā)設(shè)計模式是“消息(事件)驅(qū)動”,它一般運用在Actor模型中:服務(wù)請求者向服務(wù)提供者發(fā)送消息,然后繼續(xù)進行后續(xù)不依賴服務(wù)處理結(jié)果的任務(wù),在需要依賴結(jié)果前終止當前流程并記錄狀態(tài);在等到回應(yīng)消息后根據(jù)記錄的狀態(tài)觸發(fā)后續(xù)流程。這種基于狀態(tài)機的并發(fā)控制雖然比回調(diào)更適合于有延續(xù)性的順序流程,但開發(fā)者卻不得不將連續(xù)流程按照異步服務(wù)的調(diào)用切斷為多個按狀態(tài)區(qū)分的子流程,這樣又人為的增加了開發(fā)的復(fù)雜性。運用Future模式可以避免這個問題,不必為了異步調(diào)用而打碎連續(xù)的流程。但是有一點應(yīng)當特別注意:Future.get()方法可能會阻塞線程的執(zhí)行,所以它通常無法直接融入常規(guī)的Actor模型中。(基于協(xié)程的Actor模型可以較好的解決這個沖突)
Future的靈活性還體現(xiàn)在其同步和異步的自由取舍,開發(fā)者可以根據(jù)流程的需要自由決定是否需要等待[Future.isDone()],何時等待[Future.get()],等待多久[Future.get(timeout)]。比如可以根據(jù)數(shù)據(jù)是否就緒而決定要不要借這個空檔完成點其它任務(wù),這對于實現(xiàn)“異步分支預(yù)測”機制是相當方便的。
Future的衍生
除了上面提到的基礎(chǔ)形態(tài)之外,F(xiàn)uture還有豐富的衍生變化,這里就列舉幾個常見的。
Lazy Future
與一般的Future不同,Lazy Future在創(chuàng)建之初不會主動開始準備引用的對象,而是等到請求對象時才開始相應(yīng)的工作。因此,Lazy Future本身并不是為了實現(xiàn)并發(fā),而是以節(jié)約不必要的運算資源為出發(fā)點,效果上與Lambda/Closure類似。例如設(shè)計某些API時,你可能需要返回一組信息,而其中某些信息的計算可能會耗費可觀的資源。但調(diào)用者不一定都關(guān)心所有的這些信息,因此將那些需要耗費較多資源的對象以Lazy Future的形式提供,可以在調(diào)用者不需要用到特定的信息時節(jié)省資源。
另外Lazy Future也可以用于避免過早的獲取或鎖定資源而產(chǎn)生的不必要的互斥。
Promise
Promise可以看作是Future的一個特殊分支,常見的Future一般是由服務(wù)調(diào)用者直接觸發(fā)異步處理流程,比如調(diào)用服務(wù)時立即觸發(fā)處理或 Lazy Future的取值時觸發(fā)處理。但Promise則用于顯式表示那些異步流程并不直接由服務(wù)調(diào)用者觸發(fā)的情景。例如Future接口的定時控制,其異步流程不是由調(diào)用者,而是由系統(tǒng)時鐘觸發(fā),再比如淘寶的分布式訂閱框架提供的Future式訂閱接口,其等待數(shù)據(jù)的可用性不是由訂閱者決定,而在于發(fā)布者何時發(fā)布或更新數(shù)據(jù)。因此,相對于標準的Future,Promise接口一般會多出一個set()或fulfill()接口。
可復(fù)用的Future
常規(guī)的Future是一次性的,也就是說當你獲得了異步的處理結(jié)果后,F(xiàn)uture對象本身就失去意義了。但經(jīng)過特殊設(shè)計的Future也可以實現(xiàn)復(fù)用,這對于可多次變更的數(shù)據(jù)顯得非常有用。例如前面提到的淘寶分布式訂閱框架所提供的Future式接口,它允許多次調(diào)用waitNext()方法(相當于Future.get()),每次調(diào)用時是否阻塞取決于在上次調(diào)用后是否又有數(shù)據(jù)發(fā)布,如果尚無更新,則阻塞直到下一次的數(shù)據(jù)發(fā)布。這樣設(shè)計的好處是,接口的使用者可以在其任何合適的時機,或者直接簡單的在獨立的線程中通過一個無限循環(huán)響應(yīng)訂閱數(shù)據(jù)的變化,同時還可兼顧其它定時任務(wù),甚至同時等待多個Future。簡化的例子如下:
for (;;) { schedule = getNextScheduledTaskTime(); while(schedule > now()) { try { data = subscription.waitNext(schedule - now()); processData(data); } catch(Exception e) {...} } doScheduledTask();}
Future的使用
首先列舉一段Thinking in Java中的代碼,這是出在并發(fā)中的Callable使用的一個例子,代碼,如下:
//: concurrency/CallableDemo.javaimport java.util.concurrent.*;import java.util.*;class TaskWithResult implements Callable{ private int id; public TaskWithResult(int id) { this.id = id; } public String call() { return "result of TaskWithResult " + id; }}public class CallableDemo { public static void main(String[] args) { ExecutorService exec = Executors.newCachedThreadPool(); ArrayListresults = new ArrayList(); for(int i = 0; i < 10; i++) results.add(exec.submit(new TaskWithResult(i))); for(Futurefs : results) try { // get() blocks until completion: System.out.println(fs.get()); } catch(InterruptedException e) { System.out.println(e); return; } catch(ExecutionException e) { System.out.println(e); } finally { exec.shutdown(); } }} /* Output:result of TaskWithResult 0result of TaskWithResult 1result of TaskWithResult 2result of TaskWithResult 3result of TaskWithResult 4result of TaskWithResult 5result of TaskWithResult 6result of TaskWithResult 7result of TaskWithResult 8result of TaskWithResult 9*///:~
解釋一下Future的使用過程,首先ExecutorService對象exec調(diào)用submit()方法會產(chǎn)生Future對象,他用Callable返回結(jié)果的特定類型進行了參數(shù)化。你可以使用isDone()方法來查詢Future是否已經(jīng)完成。當任務(wù)完成時,他具有一個結(jié)果,你可以調(diào)用get()方法獲取結(jié)果。你也可以不用isDone()進行檢查直接調(diào)用get(),在這種情況下,get()將會阻塞知道結(jié)果準備就緒。你可以在調(diào)用具有超時的get()函數(shù),或者先調(diào)用isDone()來查看任務(wù)是否完成,然后再調(diào)用get()。
【簡單講解Java的Future編程模式方案】相關(guān)文章:
java面向?qū)ο缶幊讨v解04-02
解析Java的設(shè)計模式編程之解釋器模式的運用04-02
講解Java的Socket網(wǎng)絡(luò)編程的多播與廣播實現(xiàn)04-02
C語言編程中使用設(shè)計模式中的原型模式的講解04-01
java教程之Java編程基礎(chǔ)12-03
Java語言的編程特點03-18
Java編程學(xué)習(xí)示例04-03
java ClassLoader機制講解04-02