多線程:
說(shuō)到多線程,最先提到的就是Thread和Runnable。實(shí)現(xiàn)多線程可以通過(guò)繼承Thread 或者 實(shí)現(xiàn)Ruannale接口實(shí)現(xiàn)。在實(shí)際開發(fā)中,大多以實(shí)現(xiàn)Runnable為主,主要是因?yàn)閷?shí)現(xiàn)Runnable比繼承Thread有以下兩個(gè)好處:
1.Runnable是接口,Thread是類。一個(gè)java類可以實(shí)現(xiàn)多個(gè)接口,可是只能繼承一個(gè)父類。所以實(shí)現(xiàn)Runnable可以避免單繼承的局限。
2.適合資源的共享。最基本的買票的例子:
通過(guò)Thread完成:
如果使用實(shí)現(xiàn)Runnable的方式:
線程池:
我們?cè)谔幚矸?wù)的時(shí)候,不可能自己創(chuàng)建線程來(lái)處理任務(wù),一方面是因?yàn)楣芾砭€程太繁瑣,另一方面防止創(chuàng)建太多線程,導(dǎo)致過(guò)多消耗內(nèi)存或頻繁切換導(dǎo)致內(nèi)存資源不足。所以一般都是通過(guò)線程池來(lái)實(shí)現(xiàn)對(duì)線程的管理。
Java通過(guò)Executors提供四種線程池,分別為:
newCachedThreadPool創(chuàng)建一個(gè)可緩存線程池,如果線程池長(zhǎng)度超過(guò)處理需要,可靈活回收空閑線程,若無(wú)可回收,則新建線程。
newFixedThreadPool 創(chuàng)建一個(gè)定長(zhǎng)線程池,可控制線程最大并發(fā)數(shù),超出的線程會(huì)在隊(duì)列中等待。定長(zhǎng)線程池的長(zhǎng)度一般根據(jù)系統(tǒng)資源進(jìn)行設(shè)置。如Runtime.getRuntime().availableProcessors().這樣可以充分利用線程資源。
newScheduledThreadPool 創(chuàng)建一個(gè)定長(zhǎng)線程池,支持定時(shí)及周期性任務(wù)執(zhí)行。
newSingleThreadExecutor 創(chuàng)建一個(gè)單線程化的線程池,它只會(huì)用唯一的工作線程來(lái)執(zhí)行任務(wù),保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級(jí))執(zhí)行。還有newSingleScheduleThreadExecutor,結(jié)合了3和4.
下面看一個(gè)例子:
可以看到雖然開了線程池的容量為3的條件下啟動(dòng)4個(gè)線程,每次都是打印3個(gè)“hello”,試著把period換成7看看,是先3個(gè)“hello”,然后1個(gè)“hello”,然后2個(gè)“hello”。
Callable 和 Future
以上討論的都是Runnable,它是沒(méi)有返回值的。但是如果有場(chǎng)景是要求一個(gè)線程依賴某一個(gè)線程的返回結(jié)果,這就需要用到Callable。Callable的call方法可以有返回值,而Runnable的run方法不能有返回值,這是兩這的核心區(qū)別。
那么怎么得到Callable的返回值呢?是通過(guò)Future。Future可以監(jiān)視目標(biāo)線程調(diào)用call的情況,當(dāng)你調(diào)用Future的get()方法以獲得結(jié)果時(shí),當(dāng)前線程就開始阻塞,直接call方法結(jié)束返回結(jié)果。
這邊還要說(shuō)到另一個(gè)類:FutureTask。FutureTask實(shí)現(xiàn)了兩個(gè)接口,Runnable和Future,所以它既可以作為Runnable被線程執(zhí)行,又可以作為Future得到Callable的返回值。關(guān)于FutureTask詳解可以閱讀http://blog.csdn.net/codershamo/article/details/51901057,最好是看源碼。
還是看一個(gè)例子吧。
假設(shè)有一個(gè)很耗時(shí)的返回值需要計(jì)算,并且這個(gè)返回值不是立刻需要的話,那么就可以使用FutureTask,用另一個(gè)線程去計(jì)算返回值,而當(dāng)前線程在使用這個(gè)返回值之前可以做其它的操作,等到需要這個(gè)返回值時(shí),再通過(guò)Future得到。很高效。
下面還有另一種方式使用Callable和Future。通過(guò)ExecutorService的submit方法執(zhí)行Callable,并返回Future。看例子:
現(xiàn)在需要執(zhí)行多個(gè)帶返回值得任務(wù),怎么辦?第一種方法:就是創(chuàng)建一個(gè)Future類型的集合,用Executor提交任務(wù)的返回值添加到集合中,最后遍歷取出結(jié)合。第二種使用CompletionService。下面看例子,比較兩者的執(zhí)行效率。
可以看到這種方式是按照添加的集合的順序依次執(zhí)行的。下面看方法二的代碼:
可以看到先執(zhí)行callable3,最后執(zhí)行的callable2。時(shí)間比使用Future集合要少。那是因?yàn)镃ompletionService內(nèi)部使用了BlockingQueue,負(fù)責(zé)保存異步任務(wù)的執(zhí)行結(jié)果,take的時(shí)候從BlockingQueue中取執(zhí)行結(jié)束的Future。所以更精確地完成任務(wù)。又方便性能又好呢。
CompletableFuture
對(duì)于Future,我們通過(guò)輪詢isDone,確認(rèn)完成后,調(diào)用get()獲取值,要么調(diào)用get()設(shè)置一個(gè)超時(shí)時(shí)間。但是這個(gè)get()方法會(huì)阻塞住調(diào)用線程,這種阻塞的方式實(shí)際上沒(méi)有完全實(shí)現(xiàn)異步編程。CompletableFuture類實(shí)現(xiàn)了CompletionStage和Future接口。它可以在get()阻塞的時(shí)候回調(diào)處理。CompletableFuture是Java8引入的新特性,下次和lambda一起做筆記吧。因?yàn)槭忠褍鼋┌ ?/p>