Akka系列(五):Java和Scala中的Future

隨著CPU的核數(shù)的增加坠敷,異步編程模型在并發(fā)領(lǐng)域中的得到了越來(lái)越多的應(yīng)用匈棘,由于Scala是一門(mén)函數(shù)式語(yǔ)言厂捞,天然的支持異步編程模型睁本,今天主要來(lái)看一下Java和Scala中的Futrue场靴,帶你走入異步編程的大門(mén)啡莉。

Future

很多同學(xué)可能會(huì)有疑問(wèn),F(xiàn)utrue跟異步編程有什么關(guān)系旨剥?從Future的表面意思是未來(lái)咧欣,一個(gè)Future對(duì)象可以看出一個(gè)將來(lái)得到的結(jié)果,這就和異步執(zhí)行的概念很像轨帜,你只管自己去執(zhí)行魄咕,只要將最終的結(jié)果傳達(dá)給我就行,線程不必一直暫停等待結(jié)果蚌父,可以在具體異步任務(wù)執(zhí)行的時(shí)候去執(zhí)行其他操作哮兰,舉個(gè)例子:

async-work.png

我們現(xiàn)在在執(zhí)行做飯這么一個(gè)任務(wù),它需要煮飯苟弛,燒菜喝滞,擺置餐具等操作,如果我們通過(guò)異步這種概念去執(zhí)行這個(gè)任務(wù)膏秫,比如煮飯可能需要比較久的時(shí)間右遭,但煮飯這個(gè)過(guò)程又不需要我們管理,我們可以利用這段時(shí)間去燒菜荔睹,燒菜過(guò)程中也可能有空閑時(shí)間狸演,我們可以去擺置餐具,當(dāng)電飯鍋通知我們飯燒好了僻他,菜也燒好了宵距,最后我們就可以開(kāi)始吃飯了,所以說(shuō)吨拗,上面的“煮飯 -> 飯”满哪,“燒菜 -> 菜”都可以看成一個(gè)Future的過(guò)程。

Java中的Future

在Java的早期版本中劝篷,我們不能得到線程的執(zhí)行結(jié)果哨鸭,不管是繼承Thread類(lèi)還是實(shí)現(xiàn)Runnable接口,都無(wú)法獲取線程的執(zhí)行結(jié)果娇妓,所以我們只能在線程執(zhí)行的run方法里去做相應(yīng)的一些業(yè)務(wù)邏輯操作像鸡,但隨著Java5的發(fā)布,它為了我們帶來(lái)了Callable和Future接口哈恰,我們可以利用這兩個(gè)接口的特性來(lái)獲取線程的執(zhí)行結(jié)果只估。

Callable接口

通俗的講志群,Callable接口也是一個(gè)線程執(zhí)行類(lèi)接口,那么它跟Runnable接口有什么區(qū)別呢蛔钙?我們先來(lái)看看它們兩個(gè)的定義:

1.Callable接口:

@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

2.Runnable接口:

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

從上面的定義锌云,我們可以看出,兩者最大的區(qū)別就是對(duì)應(yīng)的執(zhí)行方法是否有返回值吁脱。Callable接口中call方法具有返回值桑涎,這便是為什么我們可以通過(guò)Callable接口來(lái)得到一個(gè)線程執(zhí)行的返回值或者是異常信息。

Future接口

上面說(shuō)到既然Callable接口能返回線程執(zhí)行的結(jié)果兼贡,那么為什么還需要Future接口呢攻冷?因?yàn)镃allable接口執(zhí)行的結(jié)果只是一個(gè)將來(lái)的結(jié)果值,我們?nèi)羰切枰玫骄唧w的結(jié)果就必須利用Future接口紧显,另外Callable接口需要委托ExecutorService的submit提交任務(wù)去執(zhí)行讲衫,我們來(lái)看看它是如何定義的:

 <T> Future<T> submit(Callable<T> task);
 
 public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }
 

從submit的方法定義也可以看出它的返回值是一個(gè)Future接口類(lèi)型的值缕棵,這里其實(shí)是RunnableFuture接口孵班,這是一個(gè)很重要的接口,我們來(lái)看一下它的定義:

public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

這個(gè)接口分別繼承了Runnable和Future接口招驴,而FutureTask又實(shí)現(xiàn)了RunnableFuture接口篙程,它們之間的關(guān)系:

future-runnable.png

RunnableFuture有以下兩個(gè)特點(diǎn):

  • 繼承Runnable接口,還是以run方法作為線程執(zhí)行入口别厘,其實(shí)上面submit方法的具體實(shí)現(xiàn)也可以看出虱饿,一個(gè)Callable的Task再執(zhí)行的時(shí)候會(huì)被包裝成RunnableFuture,然后以FutureTask作為實(shí)現(xiàn)類(lèi)触趴,執(zhí)行FutureTask時(shí)氮发,還是執(zhí)行其的run方法,只不過(guò)run方法里面的業(yè)務(wù)邏輯是由我們定義的call方法的內(nèi)容冗懦,當(dāng)然再執(zhí)行run方法時(shí)爽冕,程序會(huì)自動(dòng)將call方法的執(zhí)行結(jié)果幫我們包裝起來(lái),對(duì)外部表現(xiàn)成一個(gè)Future對(duì)象披蕉。

  • 繼承Future接口颈畸,通過(guò)實(shí)現(xiàn)Future接口中的方法更新或者獲取線程的的執(zhí)行狀態(tài),比如其中的cancel(),isDone(),get()等方法没讲。

Future程序示例與結(jié)果獲取

下面是一個(gè)簡(jiǎn)單的Future示例眯娱,我們先來(lái)看一下代碼:

ExecutorService es = Executors.newSingleThreadExecutor();
Future f = es.submit(() -> {
        System.out.println("execute call");
        Thread.sleep(1000);
        return 5;
    });
try {
    System.out.println(f.isDone()); //檢測(cè)任務(wù)是否完成
    System.out.println(f.get(2000, TimeUnit.MILLISECONDS));
    System.out.println(f.isDone()); //檢測(cè)任務(wù)是否完成
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (ExecutionException e) {
    e.printStackTrace();
} catch (TimeoutException e) {
    e.printStackTrace();
}

上面的代碼使用了lambda表達(dá)式,有興趣的同學(xué)可以自己去了解下爬凑,這里我們首先構(gòu)建了一個(gè)ExecutorService徙缴,然后利用submit提交執(zhí)行Callable接口的任務(wù)。

為什么是Callable接口呢嘁信? 其實(shí)這里我們并沒(méi)有顯示聲明Callable接口于样,這里lambda會(huì)幫我們自動(dòng)進(jìn)行類(lèi)型推導(dǎo)迁霎,首先submit接受Callable接口或Runnble接口類(lèi)型作為參數(shù),而這里我們又給定了返回值百宇,所以lambda能自動(dòng)幫我們推導(dǎo)出內(nèi)部是一個(gè)Callable接口參數(shù)考廉。

到這里我們應(yīng)該大致清楚了在Java中的得到Future,那么我們又是如何從Future中得到我們想要的值呢携御?這個(gè)結(jié)論其實(shí)很容易得出昌粤,你只需要去跑一下上面的程序即可,在利用get去獲取Future中的值時(shí)啄刹,線程會(huì)一直阻塞涮坐,直到返回值或者超時(shí),所以Future中的get方法是阻塞誓军,所以雖然利用Future似乎是異步執(zhí)行任務(wù)袱讹,但是在某些需求上還是會(huì)阻塞的,并不是真正的異步昵时,stackoverflow上有兩個(gè)討論說(shuō)明了這個(gè)問(wèn)題Future.get捷雕,without blocking when task complete,有興趣的同學(xué)可以去看看壹甥。

Scala中的Future

Scala中的Future相對(duì)于Java的Future有什么不同呢救巷?我總結(jié)了一下幾點(diǎn):

1.創(chuàng)建Future變得很容易

異步編程作為函數(shù)式語(yǔ)言的一大優(yōu)勢(shì),Scala對(duì)于Future的支持也是非常棒的句柠,首先它也提供了Futrue接口浦译,但不同的是我們?cè)跇?gòu)建Future對(duì)象是不用像Java一樣那么繁瑣,并且非常簡(jiǎn)單溯职,舉個(gè)例子:

import scala.concurrent._ 
import ExecutionContext.Implicits.global 

val f: Future[String] = Future { "Hello World!" }

是不是非常簡(jiǎn)單精盅,也大大降低了我們使用Future的難度。

2.提供真正異步的Future

前面我們也說(shuō)到谜酒,Java中的Future并不是全異步的叹俏,當(dāng)你需要Future里的值的時(shí)候,你只能用get去獲取它甚带,亦或者不斷訪問(wèn)Future的狀態(tài)她肯,若完成再去取值,但其意義上便不是真正的異步了鹰贵,它在獲取值的時(shí)候是一個(gè)阻塞的操作晴氨,當(dāng)然也就無(wú)法執(zhí)行其他的操作,直到結(jié)果返回碉输。

但在Scala中籽前,我們無(wú)需擔(dān)心,雖然它也提供了類(lèi)似Java中獲取值的方式,比如:

Future Java Scala
判斷任務(wù)是否完成 isDone isCompleted
獲取值 get value

但是我們并不推薦這么做枝哄,因?yàn)檫@么做又回到了Java的老路上了肄梨,在Scala中我們可以利用Callback來(lái)獲取它的結(jié)果:

val fut = Future {
    Thread.sleep(1000)
    1 + 1
}

fut onComplete {
    case Success(r) => println(s"the result is ${r}")
    case _ => println("some Exception")
}

println("I am working")
Thread.sleep(2000)

這是一個(gè)簡(jiǎn)單的例子,F(xiàn)uture在執(zhí)行完任務(wù)后會(huì)進(jìn)行回調(diào)挠锥,這里使用了onComplete众羡,也可以注冊(cè)多個(gè)回調(diào)函數(shù),但不推薦那么做蓖租,因?yàn)槟悴荒鼙WC這些回調(diào)函數(shù)的執(zhí)行順序粱侣,其他的一些回調(diào)函數(shù)基本都是基于onComplete的,有興趣的同學(xué)可以去閱讀一下Future的源碼蓖宦。

我們先來(lái)看一下它的運(yùn)行結(jié)果:

I am working
the result is 2

從結(jié)果中我們可以分析得出齐婴,我們?cè)诶肅allback方式來(lái)獲取Future結(jié)果的時(shí)候并不會(huì)阻塞,而只是當(dāng)Future完成后會(huì)自動(dòng)調(diào)用onComplete稠茂,我們只需要根據(jù)它的結(jié)果再做處理即可柠偶,而其他互不依賴(lài)的操作可以繼續(xù)執(zhí)行不會(huì)阻塞。

3.強(qiáng)大的Future組合

前面我們講的較多的都是單個(gè)Future的情況睬关,但是在真正實(shí)際應(yīng)用時(shí)往往會(huì)遇到多個(gè)Future的情況诱担,那么在Scala中是如何處理這種情況的呢?

Scala中的有多種方式來(lái)組合Future,那我們就來(lái)看看這些方式吧共螺。

1.flatMap

我們可以利用flatMap來(lái)組合多個(gè)Future该肴,不多說(shuō)情竹,先上代碼:

val fut1 = Future {
    println("enter task1")
    Thread.sleep(2000)
    1 + 1
}

val fut2 = Future {
    println("enter task2")
    Thread.sleep(1000)
    2 + 2
}

fut1.flatMap { v1 =>
    fut2.map { v2 =>
      println(s"the result is ${v1 + v2}")
    }
}
Thread.sleep(2500)

利用flatMap確實(shí)能組合Future藐不,但代碼的閱讀性實(shí)在是有點(diǎn)差,你能想象5個(gè)甚至10個(gè)map層層套著么秦效,所以我們并不推薦這么做雏蛮,但是我們需要了解這種方式,其他簡(jiǎn)潔的方式可能最終轉(zhuǎn)化成的版本也許就是這樣的阱州。

2.for yield表達(dá)式

我們只是把上面關(guān)于flatMap的代碼替換一下挑秉,看下面:

for {
    v1 <- fut1
    v2 <- fut2
} yield println(s"the result is ${v1 + v2}")

看上去是不是比之前的方式簡(jiǎn)潔多了,這也是我們?cè)诿鎸?duì)Future組合時(shí)推薦的方式苔货,當(dāng)然不得不說(shuō)for yield表達(dá)式是一種語(yǔ)法糖犀概,它最終還是會(huì)被翻譯成我們常見(jiàn)的方法,比如flatMap夜惭,map姻灶,filter等,感興趣的可以參考它的官方文檔诈茧。for yield表達(dá)式

3.scala-async

另外我們可以用scala-async來(lái)組裝Futrue語(yǔ)句塊产喉,示例如下:

import scala.async.Async.{async, await}

val v1 = async {
  await(fut1) + await(fut2)
}

v1 foreach {
  case r => println(s"the result is ${v1}")
}

這種方式與for yield表達(dá)式有啥區(qū)別呢?其實(shí)主要有兩點(diǎn):

  • 表達(dá)語(yǔ)意更加清晰,不需要用為中間值命名
  • 不需要<-等表達(dá)式曾沈,可減少一定的代碼量

scala-async相關(guān)的具體信息可以參考它的項(xiàng)目主頁(yè)这嚣。scala-async

總的來(lái)說(shuō)Scala中的Future確實(shí)強(qiáng)大,在實(shí)現(xiàn)真正異步的情況下塞俱,為我們提供許多方便而又簡(jiǎn)潔的操作模式姐帚,其實(shí)比如還有Future.reduce(),F(xiàn)uture.traverse(),Future.sequence()等方法障涯,這些方法的具體功能和具體使用這里就不講了卧土,但相關(guān)的示例代碼都會(huì)在我的示例工程里,有興趣的同學(xué)可以去跑跑加深理解像樊。源碼鏈接

總結(jié)

這篇文章主要講解了JVM生態(tài)上兩大語(yǔ)言Java和Scala在異步編程上的一些表現(xiàn)尤莺,這里主要是Future機(jī)制,在清楚明白它的概念后生棍,我們才能寫(xiě)出更好的程序颤霎,雖然本篇文章沒(méi)有涉及到Akka相關(guān)的內(nèi)容,但是Akka本身是用Scala寫(xiě)的涂滴,而且大量使用了Scala中的Future友酱,相信通過(guò)對(duì)Future的學(xué)習(xí),對(duì)Akka的理解會(huì)有一定的幫助柔纵。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末缔杉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子搁料,更是在濱河造成了極大的恐慌或详,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件郭计,死亡現(xiàn)場(chǎng)離奇詭異霸琴,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)昭伸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)梧乘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人庐杨,你說(shuō)我怎么就攤上這事选调。” “怎么了灵份?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵仁堪,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我各吨,道長(zhǎng)枝笨,這世上最難降的妖魔是什么袁铐? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮横浑,結(jié)果婚禮上剔桨,老公的妹妹穿的比我還像新娘。我一直安慰自己徙融,他們只是感情好洒缀,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著欺冀,像睡著了一般树绩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上隐轩,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天饺饭,我揣著相機(jī)與錄音,去河邊找鬼职车。 笑死瘫俊,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的悴灵。 我是一名探鬼主播扛芽,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼积瞒!你這毒婦竟也來(lái)了川尖?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤茫孔,失蹤者是張志新(化名)和其女友劉穎叮喳,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體银酬,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嘲更,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了揩瞪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡篓冲,死狀恐怖李破,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情壹将,我是刑警寧澤嗤攻,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站诽俯,受9級(jí)特大地震影響妇菱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一闯团、第九天 我趴在偏房一處隱蔽的房頂上張望辛臊。 院中可真熱鬧,春花似錦房交、人聲如沸彻舰。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)刃唤。三九已至,卻和暖如春白群,著一層夾襖步出監(jiān)牢的瞬間尚胞,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工帜慢, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留辐真,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓崖堤,卻偏偏與公主長(zhǎng)得像侍咱,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子密幔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 隨著CPU的核數(shù)的增加楔脯,異步編程模型在并發(fā)領(lǐng)域中的得到了越來(lái)越多的應(yīng)用,由于Scala是一門(mén)函數(shù)式語(yǔ)言胯甩,天然的支持...
    java部落閱讀 273評(píng)論 0 0
  • Android Handler機(jī)制系列文章整體內(nèi)容如下: Android Handler機(jī)制1之ThreadAnd...
    隔壁老李頭閱讀 4,255評(píng)論 2 12
  • 下面是我自己收集整理的Java線程相關(guān)的面試題偎箫,可以用它來(lái)好好準(zhǔn)備面試木柬。 參考文檔:-《Java核心技術(shù) 卷一》-...
    阿呆變Geek閱讀 14,828評(píng)論 14 507
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)淹办,斷路器眉枕,智...
    卡卡羅2017閱讀 134,654評(píng)論 18 139
  • 當(dāng)你初入職場(chǎng)時(shí)速挑,你犯的所有錯(cuò)誤都會(huì)被原諒。但是這并不意味著副硅,這些錯(cuò)誤值得被原諒姥宝。 ?01 我的老板告訴我,同屬于犯...
    彭大陽(yáng)陽(yáng)閱讀 545評(píng)論 2 3