摘要:隨著的核數(shù)的增加,異步編程模型在并發(fā)領(lǐng)域中的得到了越來(lái)越多的應(yīng)用,由于是一門(mén)函數(shù)式語(yǔ)言,天然的支持異步編程模型,今天主要來(lái)看一下和中的,帶你走入異步編程的大門(mén)。
隨著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è)例子:
我們現(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{ /** * 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)看看它是如何定義的:
Future submit(Callable task); public Future submit(Callable task) { if (task == null) throw new NullPointerException(); RunnableFuture ftask = newTaskFor(task); execute(ftask); return ftask; }
從submit的方法定義也可以看出它的返回值是一個(gè)Future接口類(lèi)型的值,這里其實(shí)是RunnableFuture接口,這是一個(gè)很重要的接口,我們來(lái)看一下它的定義:
public interface RunnableFutureextends Runnable, Future { /** * 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)系:
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中的FutureScala中的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)看看這些方式吧。
我們可以利用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)化成的版本也許就是這樣的。
我們只是把上面關(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á)式
總的來(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)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/70069.html
摘要:本文介紹和點(diǎn)評(píng)上的等并發(fā)編程模型。異步更適合并發(fā)編程。同步使線程阻塞,導(dǎo)致等待。基本模型這是最簡(jiǎn)單的模型,創(chuàng)建線程來(lái)執(zhí)行一個(gè)任務(wù),完畢后銷(xiāo)毀線程。響應(yīng)式編程是一種面向數(shù)據(jù)流和變化傳播的編程模式。起源于電信領(lǐng)域的的編程模型。 本文介紹和點(diǎn)評(píng)JVM上的Thread, Thread Pool, Future, Rx, async-await, Fiber, Actor等并發(fā)編程模型。本人經(jīng)驗(yàn)...
摘要:本文介紹和點(diǎn)評(píng)上的等并發(fā)編程模型。異步更適合并發(fā)編程。同步使線程阻塞,導(dǎo)致等待。基本模型這是最簡(jiǎn)單的模型,創(chuàng)建線程來(lái)執(zhí)行一個(gè)任務(wù),完畢后銷(xiāo)毀線程。響應(yīng)式編程是一種面向數(shù)據(jù)流和變化傳播的編程模式。起源于電信領(lǐng)域的的編程模型。 本文介紹和點(diǎn)評(píng)JVM上的Thread, Thread Pool, Future, Rx, async-await, Fiber, Actor等并發(fā)編程模型。本人經(jīng)驗(yàn)...
摘要:一界面框架是微軟在其最新桌面操作系統(tǒng)中使用的圖形用戶界面。干貨盤(pán)點(diǎn)二服務(wù)在寫(xiě)后臺(tái)代碼的過(guò)程中,經(jīng)常會(huì)遇到要寫(xiě)一些多帶帶的服務(wù)。這個(gè)傳統(tǒng)的控件開(kāi)發(fā)起來(lái)很不方面,使用也不友好。發(fā)現(xiàn)有用的,這個(gè)第三方的框架,集成的很好,用起來(lái)也方便。一、Fluent Ribbon界面框架Fluent/Ribbon是微軟在其最新桌面操作系統(tǒng)Windows 7中使用的圖形用戶界面。 Windows平臺(tái)的進(jìn)化,伴隨著系...
摘要:源碼鏈接進(jìn)階持久化插件有同學(xué)可能會(huì)問(wèn),我對(duì)不是很熟悉亦或者覺(jué)得單機(jī)存儲(chǔ)并不是安全,有沒(méi)有支持分布式數(shù)據(jù)存儲(chǔ)的插件呢,比如某爸的云數(shù)據(jù)庫(kù)答案當(dāng)然是有咯,良心的我當(dāng)然是幫你們都找好咯。 這次把這部分內(nèi)容提到現(xiàn)在寫(xiě),是因?yàn)檫@段時(shí)間開(kāi)發(fā)的項(xiàng)目剛好在這一塊遇到了一些難點(diǎn),所以準(zhǔn)備把經(jīng)驗(yàn)分享給大家,我們?cè)谑褂肁kka時(shí),會(huì)經(jīng)常遇到一些存儲(chǔ)Actor內(nèi)部狀態(tài)的場(chǎng)景,在系統(tǒng)正常運(yùn)行的情況下,我們不需要...
摘要:共享內(nèi)存相信對(duì)并發(fā)有所了解的同學(xué)都應(yīng)該知道在推出后,對(duì)內(nèi)存管理有了更高標(biāo)準(zhǔn)的規(guī)范了,這使我們開(kāi)發(fā)并發(fā)程序也有更好的標(biāo)準(zhǔn)了,不會(huì)有一些模糊的定義導(dǎo)致的無(wú)法確定的錯(cuò)誤。 通過(guò)前幾篇的學(xué)習(xí),相信大家對(duì)Akka應(yīng)該有所了解了,都說(shuō)解決并發(fā)哪家強(qiáng),JVM上面找Akka,那么Akka到底在解決并發(fā)問(wèn)題上幫我們做了什么呢? 共享內(nèi)存 眾所周知,在處理并發(fā)問(wèn)題上面,最核心的一部分就是如何處理共享內(nèi)存,...
閱讀 1702·2021-11-18 10:02
閱讀 2218·2021-11-15 11:38
閱讀 2666·2019-08-30 15:52
閱讀 2190·2019-08-29 14:04
閱讀 3231·2019-08-29 12:29
閱讀 2086·2019-08-26 11:44
閱讀 994·2019-08-26 10:28
閱讀 830·2019-08-23 18:37