線程池

為什么要使用線程池

1.可以減少線程創(chuàng)建和銷毀帶來的性能消耗
2.提高響應(yīng)速度:當(dāng)任務(wù)到達(dá)時(shí),任務(wù)可以不需要等到線程創(chuàng)建就能立即執(zhí)行
3.提高線程的可管理性

線程池的實(shí)現(xiàn)原理

線程池的作用是有效的降低頻繁創(chuàng)建銷毀線程所帶來的額外開銷。一般來說,線程池都是采 用預(yù)創(chuàng)建的技術(shù),在應(yīng)用啟動(dòng)之初便預(yù)先創(chuàng)建一定數(shù)目的線程。應(yīng)用在運(yùn)行的過程中,需要時(shí) 可以從這些線程所組成的線程池里申請分配一個(gè)空閑的線程,來執(zhí)行一定的任務(wù)。任務(wù)完成 后,并不是將線程銷毀,而是將它返還給線程池,由線程池自行管理。
如果線程池中大多線程處于空閑狀態(tài),為了節(jié)省系統(tǒng)資源,線程池就會動(dòng)態(tài)銷毀其中一部分空閑的線程

線程池的結(jié)構(gòu)

1.Workqueue:任務(wù)隊(duì)列,用于存放待執(zhí)行任務(wù)
2.Worker:工作類,一個(gè)worker代表啟動(dòng)了一個(gè)線程,它啟動(dòng)后會循環(huán)執(zhí)行workQueue里 面的所有任務(wù)

線程池的執(zhí)行流程

1.一個(gè)任務(wù)提交,如果線程池大小沒達(dá)到corePoolSize,則每次都創(chuàng)建一個(gè)worker也就是一 個(gè)線程來立即執(zhí)行。
2.如果達(dá)到corePoolSize,則把多余的任務(wù)放到workQueue,等待已創(chuàng)建的worker來循環(huán) 執(zhí)行
3.如果隊(duì)列workQueue都放滿了還沒有執(zhí)行,則在maximumPoolSize下面創(chuàng)建新的worker 來循環(huán)執(zhí)行workQueue
4.如果啟動(dòng)到maximumPoolSize還有任務(wù)進(jìn)來,線程池已達(dá)到滿負(fù)載,此時(shí)就執(zhí)行任務(wù)拒 絕RejectedExecutionHandler
代碼:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();

    int c = ctl.get();
    //1.當(dāng)前池中線程比核心數(shù)少,新建一個(gè)線程執(zhí)行任務(wù)
    if (workerCountOf(c) < corePoolSize) {   
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    //2.核心池已滿,但任務(wù)隊(duì)列未滿,添加到隊(duì)列中
    if (isRunning(c) && workQueue.offer(command)) {   
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))    //如果這時(shí)被關(guān)閉了,拒絕任務(wù)
            reject(command);
        else if (workerCountOf(recheck) == 0)    //如果之前的線程已被銷毀完,新建一個(gè)線程
            addWorker(null, false);
    }
    //3.核心池已滿,隊(duì)列已滿,試著創(chuàng)建一個(gè)新線程
    else if (!addWorker(command, false))
        reject(command);    //如果創(chuàng)建新線程失敗了,說明線程池被關(guān)閉或者線程池完全滿了,拒絕任務(wù)
}
線程池的主要應(yīng)用場景

1.需要大量的線程來完成任務(wù),且完成任務(wù)的時(shí)間比較短,如WEB服務(wù)器完成網(wǎng)頁請求這 樣的任務(wù)。因?yàn)閱蝹€(gè)任務(wù)小,而任務(wù)數(shù)量巨大。
2.對性能要求苛刻的應(yīng)用,比如要求服務(wù)器迅速響應(yīng)客戶請求

ThreadPoolExecutor類

參數(shù)

1.keepAliveTime:代表的就是線程空閑后多久后銷毀,線程的銷毀是通過worker的 getTask()來實(shí)現(xiàn)的。一般來說,Worker會循環(huán)獲取getTask(),如果getTask()返回null則 工作線程worker終結(jié)。

  1. corePoolSize(線程池的基本大小):核心線程數(shù),核心線程會一直存活,即使沒有任務(wù)需 要處理。當(dāng)線程數(shù)小于核心線程數(shù)時(shí),即使現(xiàn)有的線程空閑,線程池也會優(yōu)先創(chuàng)建新線 程來處理任務(wù),而不是直接交給現(xiàn)有的線程處理。
  2. maximumPoolSize(線程池最大大小):當(dāng)線程數(shù)大于或等于核心線程數(shù),且任務(wù)隊(duì)列已 滿時(shí),線程池會創(chuàng)建新的線程,直到線程數(shù)量達(dá)到maximumPoolSize。如果線程數(shù)已等于 maximumPoolSize,且任務(wù)隊(duì)列已滿,則已超出線程池的處理能力,線程池會拒絕處理任務(wù)而 拋出異常。
  3. queueCapacity(任務(wù)隊(duì)列容量):從maxPoolSize的描述上可以看出,任務(wù)隊(duì)列的容量會影 響到線程的變化,因此任務(wù)隊(duì)列的長度也需要恰當(dāng)?shù)脑O(shè)置。
  4. timeunit:參數(shù)keepAliveTime的時(shí)間單位,有7種取值,在TimeUnit類中有7種靜態(tài)屬性。
  5. RunnableTaskQueue:一個(gè)阻塞隊(duì)列,用來存儲等待執(zhí)行的任務(wù),這個(gè)參數(shù)的選擇也很重要,會 對線程池的運(yùn)行過程產(chǎn)生重大影響,一般來說,這里的阻塞隊(duì)列有以下幾種選擇:
    i. ArrayBlockingQueue:基于數(shù)組的先進(jìn)先出隊(duì)列,此隊(duì)列創(chuàng)建時(shí)必須指定大小;
    ii. LinkedBlockingQueue:基于鏈表的先進(jìn)先出隊(duì)列,如果創(chuàng)建時(shí)沒有指定此隊(duì)列大 小,則默認(rèn)為Integer.MAX_VALUE;
    iii. SynchronousQueue:這個(gè)隊(duì)列比較特殊,它不會保存提交的任務(wù),而是將直接新建 一個(gè)線程來執(zhí)行新來的任務(wù)。
  6. threadFactory:線程工廠,主要用來創(chuàng)建線程;
  7. RejectedExecutionHandler:表示當(dāng)拒絕處理任務(wù)時(shí)的策略,有以下四種取值:
    i. ThreadPoolExecutor.AbortPolicy:丟棄任務(wù)并拋出RejectedExecutionException異 常
    ii. ThreadPoolExecutor.DiscardPolicy:也是丟棄任務(wù),但是不拋出異常。
    iii. ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊(duì)列最前面的任務(wù),然后重新嘗試執(zhí)行任務(wù)(重復(fù)此過程)
    iv. ThreadPoolExecutor.CallerRunsPolicy:由調(diào)用線程處理該任務(wù)
方法:
  1. execute():向線程池提交一個(gè)任務(wù),交由線程池去執(zhí)行。
  2. submit():來向線程池提交任務(wù)的,但是它和execute()方法不同,它能夠返回任務(wù)執(zhí)行 的結(jié)果,內(nèi)部調(diào)用的execute()方法,只不過它利用了Future來獲取任務(wù)執(zhí)行結(jié)果。
  3. shutdown():不會立即終止線程池,而是要等所有任務(wù)緩存隊(duì)列中的任務(wù)都執(zhí)行完后才 終止,但再也不會接受新的任務(wù)
  4. shutdownNow():立即終止線程池,并嘗試打斷正在執(zhí)行的任務(wù),并且清空任務(wù)緩存隊(duì) 列,返回尚未執(zhí)行的任務(wù)
線程池的調(diào)優(yōu)

1.調(diào)整線程池的大小-線程池的最佳大小取決于可用處理器的數(shù)目以及工作隊(duì)列中的任務(wù)的性質(zhì)
2.CPU密集型任務(wù),線程數(shù)目少一些,例如CPU+1個(gè)
3.I/O密集型任務(wù),線程多一些,例如2*CPU個(gè)
4.混合型任務(wù),如果可以拆分,將其拆分成一個(gè)CPU密集型任務(wù)和一個(gè)I/O密集型任務(wù),只要兩個(gè)任務(wù)執(zhí)行時(shí)間相差不是特別大,那么分解后執(zhí)行的吞吐量要比串行的高,如果任務(wù)執(zhí)行時(shí)間相差太大就不用分解,用Runtime.getRuntime().availableProcessors()獲取當(dāng)前設(shè)備CPU數(shù)目

使用線程池的注意事項(xiàng)

(1) 線程池的大小。 多線程應(yīng)用并非線程越多越好, 需要根據(jù)系統(tǒng)運(yùn)行的軟硬件環(huán)境以及應(yīng)用本身的特點(diǎn)決定線程池的大小。 一般來說, 如果代碼結(jié)構(gòu)合理的話, 線程數(shù)目與 CPU數(shù)量相適合即可。 如果線程運(yùn)行時(shí)可能出現(xiàn)阻塞現(xiàn)象, 可相應(yīng)增加池的大??; 如有必要可采用自適應(yīng)算法來動(dòng)態(tài)調(diào)整線程池的大小, 以提高 CPU 的有效利用率和系統(tǒng)的整體性能。
(2) 并發(fā)錯(cuò)誤。 多線程應(yīng)用要特別注意并發(fā)錯(cuò)誤, 要從邏輯上保證程序的正確性, 注意避免死鎖現(xiàn)象的發(fā)生。
(3) 線程泄漏。 這是線程池應(yīng)用中一個(gè)嚴(yán)重的問題, 當(dāng)任務(wù)執(zhí)行完畢而線程沒能返回池中就會發(fā)生線程泄漏現(xiàn)象。

常用的線程池

image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容