多線程編程是業(yè)務(wù)開發(fā)中要用到的一項(xiàng)技術(shù),盡管面臨著一些挑戰(zhàn)劫拢,但多線程開發(fā)也有著很多優(yōu)點(diǎn)芜赌,例如資源利用率更好咖耘,程序設(shè)計(jì)在某些情況下更簡(jiǎn)單,程序響應(yīng)更快等等。接下來(lái)主要說(shuō)一下線程的幾種創(chuàng)建方式以及線程池的應(yīng)用维蒙。
1通過(guò)繼承Thread的方式來(lái)創(chuàng)建線程
2通過(guò)實(shí)現(xiàn)Runnable接口來(lái)創(chuàng)建線程
這兩種方式本質(zhì)上其實(shí)是一樣的掰吕,都是通過(guò)run()方法,深挖源碼的話颅痊,第一種方式中Thread其實(shí)也是實(shí)現(xiàn)了Runnable接口殖熟,然后重寫run()方法。然而這兩種方式有一個(gè)不足斑响,就是run()方法里并不能返回一個(gè)結(jié)果和拋出異常菱属,所以就有了第三種創(chuàng)建線程的方式,通過(guò)Callable接口恋捆,并用FutureTask來(lái)接收返回的對(duì)象照皆。
3通過(guò)Callable接口,并用FutureTask來(lái)接收返回的對(duì)象
和Runnable接口不一樣沸停,Callable接口提供了一個(gè)call()方法作為線程執(zhí)行體膜毁,call()方法比run()方法功能要強(qiáng)大。call()方法可以有返回值愤钾,call()方法可以聲明拋出異常
Java5提供了Future接口來(lái)代表Callable接口里call()方法的返回值瘟滨,并且為Future接口提供了一個(gè)實(shí)現(xiàn)類FutureTask,這個(gè)實(shí)現(xiàn)類既實(shí)現(xiàn)了Future接口能颁,還實(shí)現(xiàn)了Runnable接口杂瘸,因此可以作為Thread類的target。在Future接口里定義了幾個(gè)公共方法來(lái)控制它關(guān)聯(lián)的Callable任務(wù)伙菊。
接下來(lái)說(shuō)線程池败玉,線程池是管理線程的地方,在一個(gè)項(xiàng)目中镜硕,我們知道需要多次的創(chuàng)建線程和銷毀線程运翼,然而創(chuàng)建和銷毀是及其耗費(fèi)內(nèi)存資源的,所以就有了線程池的需要兴枯,來(lái)幫我們管理調(diào)度線程血淌,省下了很多的內(nèi)存資源。
我們應(yīng)該如何創(chuàng)建一個(gè)線程池那?Java中已經(jīng)提供了創(chuàng)建線程池的一個(gè)類:Executor
而我們創(chuàng)建時(shí)财剖,一般使用它的子類:ThreadPoolExecutor悠夯。但是從ThreadPoolExecutor到Executor并不是簡(jiǎn)單的實(shí)現(xiàn)或繼承關(guān)系,兩者中間有好多中間類躺坟。下面是各類之間的層級(jí)關(guān)系調(diào)用實(shí)現(xiàn)類圖
我把這幾個(gè)類用黑色線條畫了起來(lái)沦补,發(fā)現(xiàn)我們需要的ThreadPoolExecutor首先繼承了抽象類AbstractExecutorService,然后AbstractExecutorService實(shí)現(xiàn)了ExecutorService接口咪橙,最ExecutorService接口又實(shí)現(xiàn)了Executor接口策彤。
我們?cè)賮?lái)看一下ThreadPoolExecutor構(gòu)造函數(shù)的參數(shù)
我們可以看出栓袖,線程池中的corePoolSize就是線程池中的核心線程數(shù)量,這幾個(gè)核心線程店诗,只是在沒有用的時(shí)候裹刮,也不會(huì)被回收,maximumPoolSize就是線程池中可以容納的最大線程的數(shù)量庞瘸,而keepAliveTime捧弃,就是線程池中除了核心線程之外的其他的最長(zhǎng)可以保留的時(shí)間,因?yàn)樵诰€程池中擦囊,除了核心線程即使在無(wú)任務(wù)的情況下也不能被清除违霞,其余的都是有存活時(shí)間的,意思就是非核心線程可以保留的最長(zhǎng)的空閑時(shí)間瞬场,而util买鸽,就是計(jì)算這個(gè)時(shí)間的一個(gè)單位,workQueue贯被,就是等待隊(duì)列眼五,任務(wù)可以儲(chǔ)存在任務(wù)隊(duì)列中等待被執(zhí)行,執(zhí)行的是FIFIO原則(先進(jìn)先出)彤灶。threadFactory看幼,就是創(chuàng)建線程的線程工廠,最后一個(gè)handler,是一種拒絕策略幌陕,我們可以在任務(wù)滿了之后诵姜,拒絕執(zhí)行某些任務(wù),具體的拒絕策略如下:
ThreadPoolExecutor.AbortPolicy:丟棄任務(wù)并拋出RejectedExecutionException異常搏熄。
ThreadPoolExecutor.DiscardPolicy:也是丟棄任務(wù)棚唆,但是不拋出異常。
ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊(duì)列最前面的任務(wù)心例,然后重新嘗試執(zhí)行任務(wù)(重復(fù)此過(guò)程)
ThreadPoolExecutor.CallerRunsPolicy:由調(diào)用線程處理該任務(wù)
再來(lái)看線程池的執(zhí)行流程
我們可以看出宵凌,任務(wù)進(jìn)來(lái)時(shí),首先執(zhí)行判斷契邀,判斷核心線程是否處于空閑狀態(tài)摆寄,如果不是失暴,核心線程就先就執(zhí)行任務(wù)坯门,如果核心線程已滿,則判斷任務(wù)隊(duì)列是否有地方存放該任務(wù)逗扒,若果有古戴,就將任務(wù)保存在任務(wù)隊(duì)列中,等待執(zhí)行矩肩,如果滿了现恼,在判斷最大可容納的線程數(shù),如果沒有超出這個(gè)數(shù)量,就開創(chuàng)非核心線程執(zhí)行任務(wù)叉袍,如果超出了始锚,就調(diào)用handler實(shí)現(xiàn)拒絕策略。
下面一段測(cè)試代碼來(lái)說(shuō)明這個(gè)線程池執(zhí)行的流程
最后運(yùn)行結(jié)果與執(zhí)行流程是一樣的喳逛,都是先判斷核心線程瞧捌,當(dāng)核心線程最大后,在判斷隊(duì)列润文,當(dāng)隊(duì)列也滿后姐呐,在進(jìn)入非核心線程。