簡(jiǎn)析 Java 的線程池

前言

從事編程的小伙伴們脐区,必然是要接觸多線程開發(fā)的捐韩,而常見的多線程應(yīng)用程序不過是 new 一個(gè)線程(Thread)并且重寫 run 方法瘟仿,雖然能夠完成任務(wù)脊串,但是 new 的過程以及銷毀的過程總是耗時(shí)又耗費(fèi)資源的凉泄,那么有沒有一個(gè)辦法躏尉,我能夠復(fù)用這些線程,而又不會(huì)導(dǎo)致性能浪費(fèi)呢后众?

線程池

線程池是一種多線程處理形式胀糜,處理過程中將任務(wù)添加到隊(duì)列,然后在創(chuàng)建線程后自動(dòng)啟動(dòng)這些任務(wù)蒂誉。線程池線程都是后臺(tái)線程教藻。每個(gè)線程都使用默認(rèn)的堆棧大小,以默認(rèn)的優(yōu)先級(jí)運(yùn)行右锨,并處于多線程單元中括堤。如果某個(gè)線程在托管代碼中空閑(如正在等待某個(gè)事件),則線程池將插入另一個(gè)輔助線程來使所有處理器保持繁忙。如果所有線程池線程都始終保持繁忙绍移,但隊(duì)列中包含掛起的工作悄窃,則線程池將在一段時(shí)間后創(chuàng)建另一個(gè)輔助線程但線程的數(shù)目永遠(yuǎn)不會(huì)超過最大值。超過最大值的線程可以排隊(duì)蹂窖,但他們要等到其他線程完成后才啟動(dòng)轧抗。

感謝本文思路來源 《深入理解Java之線程池》,作者:海子

Java中的ThreadPoolExecutor類

java.uitl.concurrent.ThreadPoolExecutor類是線程池中最核心的一個(gè)類瞬测,因此如果要透徹地了解Java中的線程池鸦致,必須先了解這個(gè)類。下面我們來看一下ThreadPoolExecutor類的具體實(shí)現(xiàn)源碼涣楷。
在ThreadPoolExecutor類中提供了四個(gè)構(gòu)造方法:

/**
 * 非完整代碼,這里為了直觀觀察抗碰,做了簡(jiǎn)化處理
 */
public class ThreadPoolExecutor extends AbstractExecutorService {
    ...
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
        BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
    ...
}

從上面的代碼可以得知狮斗,ThreadPoolExecutor繼承了AbstractExecutorService類,并提供了四個(gè)構(gòu)造器弧蝇,事實(shí)上碳褒,通過觀察每個(gè)構(gòu)造器的源碼具體實(shí)現(xiàn),發(fā)現(xiàn)前面三個(gè)構(gòu)造器都是調(diào)用的第四個(gè)構(gòu)造器進(jìn)行的初始化工作看疗。

下面解釋下一下構(gòu)造器中各個(gè)參數(shù)的含義:

  • corePoolSize:核心池的大小沙峻,這個(gè)參數(shù)跟后面講述的線程池的實(shí)現(xiàn)原理有非常大的關(guān)系。在創(chuàng)建了線程池后两芳,默認(rèn)情況下摔寨,線程池中并沒有任何線程,而是等待有任務(wù)到來才創(chuàng)建線程去執(zhí)行任務(wù)怖辆,除非調(diào)用了prestartAllCoreThreads()或者prestartCoreThread()方法是复,從這2個(gè)方法的名字就可以看出删顶,是預(yù)創(chuàng)建線程的意思,即在沒有任務(wù)到來之前就創(chuàng)建corePoolSize個(gè)線程或者一個(gè)線程淑廊。默認(rèn)情況下逗余,在創(chuàng)建了線程池后,線程池中的線程數(shù)為0季惩,當(dāng)有任務(wù)來之后录粱,就會(huì)創(chuàng)建一個(gè)線程去執(zhí)行任務(wù),當(dāng)線程池中的線程數(shù)目達(dá)到corePoolSize后画拾,就會(huì)把到達(dá)的任務(wù)放到緩存隊(duì)列當(dāng)中啥繁;
  • maximumPoolSize:線程池最大線程數(shù),這個(gè)參數(shù)也是一個(gè)非常重要的參數(shù)碾阁,它表示在線程池中最多能創(chuàng)建多少個(gè)線程输虱;
  • keepAliveTime:表示線程沒有任務(wù)執(zhí)行時(shí)最多保持多久時(shí)間會(huì)終止。默認(rèn)情況下脂凶,只有當(dāng)線程池中的線程數(shù)大于corePoolSize時(shí)宪睹,keepAliveTime才會(huì)起作用,直到線程池中的線程數(shù)不大于corePoolSize蚕钦,即當(dāng)線程池中的線程數(shù)大于corePoolSize時(shí)亭病,如果一個(gè)線程空閑的時(shí)間達(dá)到keepAliveTime,則會(huì)終止嘶居,直到線程池中的線程數(shù)不超過corePoolSize罪帖。但是如果調(diào)用了allowCoreThreadTimeOut(boolean)方法,在線程池中的線程數(shù)不大于corePoolSize時(shí)邮屁,keepAliveTime參數(shù)也會(huì)起作用整袁,直到線程池中的線程數(shù)為0;
  • unit:參數(shù)keepAliveTime的時(shí)間單位佑吝,有7種取值坐昙,在TimeUnit類中有7種靜態(tài)屬性:
TimeUnit.DAYS;              //天
TimeUnit.HOURS;             //小時(shí)
TimeUnit.MINUTES;           //分鐘
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //納秒
  • workQueue:一個(gè)阻塞隊(duì)列,用來存儲(chǔ)等待執(zhí)行的任務(wù)芋忿,這個(gè)參數(shù)的選擇也很重要炸客,會(huì)對(duì)線程池的運(yùn)行過程產(chǎn)生重大影響,一般來說戈钢,這里的阻塞隊(duì)列有以下幾種選擇:
ArrayBlockingQueue;
PriorityBlockingQueue痹仙;
LinkedBlockingQueue;
SynchronousQueue;

ArrayBlockingQueue和PriorityBlockingQueue使用較少,一般使用LinkedBlockingQueue和SynchronousQueue殉了。線程池的排隊(duì)策略與BlockingQueue有關(guān)开仰。

  • threadFactory:線程工廠,主要用來創(chuàng)建線程;
  • handler:表示當(dāng)拒絕處理任務(wù)時(shí)的策略抖所,有以下四種取值:
ThreadPoolExecutor.AbortPolicy:丟棄任務(wù)并拋出RejectedExecutionException異常梨州。 
ThreadPoolExecutor.DiscardPolicy:也是丟棄任務(wù),但是不拋出異常田轧。 
ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊(duì)列最前面的任務(wù)暴匠,然后重新嘗試執(zhí)行任務(wù)(重復(fù)此過程)
ThreadPoolExecutor.CallerRunsPolicy:由調(diào)用線程處理該任務(wù) 

使用示例

知道了 Java 的線程池以來的 ThreadPoolExecutor 類是怎樣的一樣?xùn)|西后,我們?cè)賮斫柚褂闷饋淼木唧w例子加深認(rèn)識(shí)傻粘。

public class Test {
     public static void main(String[] args) {   
         ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
                 new ArrayBlockingQueue<Runnable>(5));
          
         for(int i=0;i<15;i++){
             MyTask myTask = new MyTask(i);
             executor.execute(myTask);
             System.out.println("線程池中線程數(shù)目:"+executor.getPoolSize()+"每窖,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:"+
             executor.getQueue().size()+",已執(zhí)行玩別的任務(wù)數(shù)目:"+executor.getCompletedTaskCount());
         }
         executor.shutdown();
     }
}
 
 
class MyTask implements Runnable {
    private int taskNum;
     
    public MyTask(int num) {
        this.taskNum = num;
    }
     
    @Override
    public void run() {
        System.out.println("正在執(zhí)行task "+taskNum);
        try {
            Thread.currentThread().sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("task "+taskNum+"執(zhí)行完畢");
    }
}

代碼都 Show 給你看了弦悉,想要加深了解窒典,就自己 Create 一個(gè)項(xiàng)目,敲進(jìn)去運(yùn)行看看結(jié)果吧稽莉!

執(zhí)行結(jié)果:

正在執(zhí)行task 0
線程池中線程數(shù)目:1瀑志,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:0,已執(zhí)行玩別的任務(wù)數(shù)目:0
線程池中線程數(shù)目:2污秆,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:0劈猪,已執(zhí)行玩別的任務(wù)數(shù)目:0
正在執(zhí)行task 1
線程池中線程數(shù)目:3,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:0良拼,已執(zhí)行玩別的任務(wù)數(shù)目:0
正在執(zhí)行task 2
線程池中線程數(shù)目:4战得,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:0,已執(zhí)行玩別的任務(wù)數(shù)目:0
正在執(zhí)行task 3
線程池中線程數(shù)目:5庸推,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:0常侦,已執(zhí)行玩別的任務(wù)數(shù)目:0
正在執(zhí)行task 4
線程池中線程數(shù)目:5,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:1贬媒,已執(zhí)行玩別的任務(wù)數(shù)目:0
線程池中線程數(shù)目:5聋亡,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:2,已執(zhí)行玩別的任務(wù)數(shù)目:0
線程池中線程數(shù)目:5际乘,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:3杀捻,已執(zhí)行玩別的任務(wù)數(shù)目:0
線程池中線程數(shù)目:5,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:4蚓庭,已執(zhí)行玩別的任務(wù)數(shù)目:0
線程池中線程數(shù)目:5,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:5仅仆,已執(zhí)行玩別的任務(wù)數(shù)目:0
線程池中線程數(shù)目:6器赞,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:5,已執(zhí)行玩別的任務(wù)數(shù)目:0
正在執(zhí)行task 10
線程池中線程數(shù)目:7墓拜,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:5港柜,已執(zhí)行玩別的任務(wù)數(shù)目:0
正在執(zhí)行task 11
線程池中線程數(shù)目:8,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:5,已執(zhí)行玩別的任務(wù)數(shù)目:0
正在執(zhí)行task 12
線程池中線程數(shù)目:9夏醉,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:5爽锥,已執(zhí)行玩別的任務(wù)數(shù)目:0
正在執(zhí)行task 13
線程池中線程數(shù)目:10,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:5畔柔,已執(zhí)行玩別的任務(wù)數(shù)目:0
正在執(zhí)行task 14
task 0執(zhí)行完畢
task 4執(zhí)行完畢
正在執(zhí)行task 5
task 1執(zhí)行完畢
task 2執(zhí)行完畢
task 3執(zhí)行完畢
正在執(zhí)行task 8
正在執(zhí)行task 7
正在執(zhí)行task 6
正在執(zhí)行task 9
task 10執(zhí)行完畢
task 13執(zhí)行完畢
task 12執(zhí)行完畢
task 11執(zhí)行完畢
task 14執(zhí)行完畢
task 5執(zhí)行完畢
task 6執(zhí)行完畢
task 7執(zhí)行完畢
task 8執(zhí)行完畢
task 9執(zhí)行完畢

從執(zhí)行結(jié)果可以看出氯夷,當(dāng)線程池中線程的數(shù)目大于5時(shí),便將任務(wù)放入任務(wù)緩存隊(duì)列里面靶擦,當(dāng)任務(wù)緩存隊(duì)列滿了之后腮考,便創(chuàng)建新的線程。如果上面程序中玄捕,將for循環(huán)中改成執(zhí)行20個(gè)任務(wù)踩蔚,就會(huì)拋出任務(wù)拒絕異常了。

不過在java doc中枚粘,并不提倡我們直接使用ThreadPoolExecutor馅闽,而是使用Executors類中提供的幾個(gè)靜態(tài)方法來創(chuàng)建線程池:

Executors.newCachedThreadPool();        //創(chuàng)建一個(gè)緩沖池,緩沖池容量大小為Integer.MAX_VALUE
Executors.newSingleThreadExecutor();   //創(chuàng)建容量為1的緩沖池
Executors.newFixedThreadPool(int);    //創(chuàng)建固定容量大小的緩沖池

下面是這三個(gè)靜態(tài)方法的具體實(shí)現(xiàn):

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

從它們的具體實(shí)現(xiàn)來看馍迄,它們實(shí)際上也是調(diào)用了ThreadPoolExecutor福也,只不過參數(shù)都已配置好了。

  • newFixedThreadPool創(chuàng)建的線程池corePoolSize和maximumPoolSize值是相等的柬姚,它使用的LinkedBlockingQueue拟杉;
  • newSingleThreadExecutor將corePoolSize和maximumPoolSize都設(shè)置為1,也使用的LinkedBlockingQueue量承;
  • newCachedThreadPool將corePoolSize設(shè)置為0搬设,將maximumPoolSize設(shè)置為Integer.MAX_VALUE,使用的SynchronousQueue撕捍,也就是說來了任務(wù)就創(chuàng)建線程運(yùn)行拿穴,當(dāng)線程空閑超過60秒,就銷毀線程忧风。

實(shí)際中默色,如果Executors提供的三個(gè)靜態(tài)方法能滿足要求,就盡量使用它提供的三個(gè)方法狮腿,因?yàn)樽约喝ナ謩?dòng)配置ThreadPoolExecutor的參數(shù)有點(diǎn)麻煩腿宰,要根據(jù)實(shí)際任務(wù)的類型和數(shù)量來進(jìn)行配置。
另外缘厢,如果ThreadPoolExecutor達(dá)不到要求吃度,可以自己繼承ThreadPoolExecutor類進(jìn)行重寫。

One More Thing

如何合理配置線程池的大小

一般需要根據(jù)任務(wù)的類型來配置線程池大刑颉:

  • 如果是CPU密集型任務(wù)椿每,就需要盡量壓榨CPU伊者,參考值可以設(shè)為 NCPU+1
  • 如果是IO密集型任務(wù),參考值可以設(shè)置為2*NCPU

當(dāng)然间护,這只是一個(gè)參考值亦渗,具體的設(shè)置還需要根據(jù)實(shí)際情況進(jìn)行調(diào)整,比如可以先將線程池大小設(shè)置為參考值汁尺,再觀察任務(wù)運(yùn)行情況和系統(tǒng)負(fù)載法精、資源利用率來進(jìn)行適當(dāng)調(diào)整。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末均函,一起剝皮案震驚了整個(gè)濱河市亿虽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌苞也,老刑警劉巖洛勉,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異如迟,居然都是意外死亡收毫,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門殷勘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來此再,“玉大人,你說我怎么就攤上這事玲销∈淠矗” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵贤斜,是天一觀的道長(zhǎng)策吠。 經(jīng)常有香客問我,道長(zhǎng)瘩绒,這世上最難降的妖魔是什么猴抹? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮锁荔,結(jié)果婚禮上蟀给,老公的妹妹穿的比我還像新娘。我一直安慰自己阳堕,他們只是感情好跋理,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著恬总,像睡著了一般前普。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上越驻,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼缀旁。 笑死记劈,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的并巍。 我是一名探鬼主播目木,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼懊渡!你這毒婦竟也來了刽射?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤剃执,失蹤者是張志新(化名)和其女友劉穎誓禁,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肾档,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡摹恰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了怒见。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片俗慈。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖遣耍,靈堂內(nèi)的尸體忽然破棺而出闺阱,到底是詐尸還是另有隱情,我是刑警寧澤舵变,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布酣溃,位于F島的核電站,受9級(jí)特大地震影響棋傍,放射性物質(zhì)發(fā)生泄漏救拉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一瘫拣、第九天 我趴在偏房一處隱蔽的房頂上張望亿絮。 院中可真熱鬧,春花似錦麸拄、人聲如沸派昧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蒂萎。三九已至,卻和暖如春淮椰,著一層夾襖步出監(jiān)牢的瞬間五慈,已是汗流浹背纳寂。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留泻拦,地道東北人毙芜。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像争拐,于是被迫代替她去往敵國(guó)和親腋粥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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

  • 轉(zhuǎn)自http://www.cnblogs.com/dolphin0520/p/3932921.html Java并...
    Allen_cyn閱讀 1,903評(píng)論 0 4
  • 前段時(shí)間遇到這樣一個(gè)問題架曹,有人問微信朋友圈的上傳圖片的功能怎么做才能讓用戶的等待時(shí)間較短隘冲,比如說一下上傳9張圖片,...
    加油碼農(nóng)閱讀 1,186評(píng)論 0 2
  • 合理利用線程池能夠帶來三個(gè)好處展辞。第一:降低資源消耗。通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗绳慎。第二:提...
    Coder_老王閱讀 3,852評(píng)論 1 4
  • 線程池作為Java中一個(gè)重要的知識(shí)點(diǎn)纵竖,看了很多文章,在此以Java自帶的線程池為例杏愤,記錄分析一下靡砌。本文參考了Jav...
    峽客閱讀 989評(píng)論 0 11
  • 合理使用線程池能夠帶來3個(gè)好處。 第一珊楼,降低資源消耗通殃。通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗。第二厕宗,...
    薛晨閱讀 6,167評(píng)論 3 9