Java多線程與高并發(fā)(五):線程池

new Thread弊端

  • 每次啟動線程都需要new Thread新建對象與線程连茧,性能差。線程池能重用存在的線程谒拴,減少對象創(chuàng)建尝江、回收的開銷。
  • 線程缺乏統(tǒng)一管理英上,可以無限制的新建線程炭序,導(dǎo)致OOM。線程池可以控制可以創(chuàng)建苍日、執(zhí)行的最大并發(fā)線程數(shù)惭聂。
  • 缺少工程實(shí)踐的一些高級的功能如定期執(zhí)行、線程中斷相恃。線程池提供定期執(zhí)行彼妻、并發(fā)數(shù)控制功能

ThreadPoolExecutor

核心變量

在創(chuàng)建線程池時(shí)需要傳入的參數(shù)

<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>

public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
                              0L, TimeUnit.MILLISECONDS,
                              new LinkedBlockingQueue<Runnable>(),
                              threadFactory);
}

  • corePoolSize:核心線程數(shù)量,線程池中應(yīng)該常駐的線程數(shù)量
  • maximumPoolSize:線程池允許的最大線程數(shù)豆茫,非核心線程在超時(shí)之后會被清除
  • workQueue:阻塞隊(duì)列,存儲等待執(zhí)行的任務(wù)
  • keepAliveTime:線程沒有任務(wù)執(zhí)行時(shí)可以保持的時(shí)間
  • unit:時(shí)間單位
  • threadFactory:線程工廠屋摇,來創(chuàng)建線程
  • rejectHandler:當(dāng)拒絕任務(wù)提交時(shí)的策略(拋異常揩魂、用調(diào)用者所在的線程執(zhí)行任務(wù)、丟棄隊(duì)列中第一個(gè)任務(wù)執(zhí)行當(dāng)前任務(wù)炮温、直接丟棄任務(wù))

創(chuàng)建線程的邏輯

以下任務(wù)提交邏輯來自ThreadPoolExecutor.execute方法:

  1. 如果運(yùn)行的線程數(shù) < corePoolSize火脉,直接創(chuàng)建新線程,即使有其他線程是空閑的
  2. 如果運(yùn)行的線程數(shù) >= corePoolSize
    2.1 如果插入隊(duì)列成功柒啤,則完成本次任務(wù)提交倦挂,但不創(chuàng)建新線程
    2.2 如果插入隊(duì)列失敗,說明隊(duì)列滿了
    2.2.1 如果當(dāng)前線程數(shù) < maximumPoolSize担巩,創(chuàng)建新的線程放到線程池中
    2.2.2 如果當(dāng)前線程數(shù) >= maximumPoolSize方援,會執(zhí)行指定的拒絕策略

阻塞隊(duì)列的策略

  • 直接提交。SynchronousQueue涛癌,它將任務(wù)直接提交給線程而不保持它們犯戏。如果不存在可用于立即運(yùn)行任務(wù)的線程送火,則試圖把任務(wù)加入隊(duì)列將失敗,因此會構(gòu)造一個(gè)新的線程先匪。此策略可以避免在處理可能具有內(nèi)部依賴性的請求集時(shí)出現(xiàn)鎖种吸。直接提交通常要求無界maximumPoolSizes 以避免拒絕新提交的任務(wù)。
  • 無界隊(duì)列呀非。使用無界隊(duì)列(例如坚俗,不具有預(yù)定義容量的 LinkedBlockingQueue)將導(dǎo)致在所有 corePoolSize線程都忙時(shí)新任務(wù)在隊(duì)列中等待。這樣岸裙,創(chuàng)建的線程就不會超過 corePoolSize猖败。(因此,maximumPoolSize的值也就無效了哥桥。)當(dāng)每個(gè)任務(wù)完全獨(dú)立于其他任務(wù)辙浑,即任務(wù)執(zhí)行互不影響時(shí),適合于使用無界隊(duì)列拟糕;例如判呕,在 Web頁服務(wù)器中。這種排隊(duì)可用于處理瞬態(tài)突發(fā)請求送滞,當(dāng)命令以超過隊(duì)列所能處理的平均數(shù)連續(xù)到達(dá)時(shí)侠草,此策略允許無界線程具有增長的可能性。
  • 有界隊(duì)列犁嗅。當(dāng)使用有限的 maximumPoolSizes 時(shí)边涕,有界隊(duì)列(如ArrayBlockingQueue)有助于防止資源耗盡,但是可能較難調(diào)整和控制褂微。隊(duì)列大小和最大池大小可能需要相互折衷:使用大型隊(duì)列和小型池可以最大限度地降低CPU 使用率功蜓、操作系統(tǒng)資源和上下文切換開銷,但是可能導(dǎo)致人工降低吞吐量宠蚂。如果任務(wù)頻繁阻塞(例如式撼,如果它們是 I/O邊界),則系統(tǒng)可能為超過您許可的更多線程安排時(shí)間求厕。使用小型隊(duì)列通常要求較大的池大小著隆,CPU使用率較高,但是可能遇到不可接受的調(diào)度開銷呀癣,這樣也會降低吞吐量美浦。

執(zhí)行線程的邏輯

如果線程能被創(chuàng)建,那么在ThreadPoolExecutor的addWorker方法中项栏,會將我們傳進(jìn)去的Runnable轉(zhuǎn)換成內(nèi)部的繼承自AQS的Worker類(new Worker(firstTask);)浦辨,在其中的run方法中不斷從任務(wù)隊(duì)列中獲取任務(wù)去執(zhí)行

關(guān)鍵方法

  • execute:提交任務(wù)
  • submit:提交任務(wù),能夠得到執(zhí)行結(jié)果
  • shutdown:等待任務(wù)執(zhí)行完再關(guān)閉線程池
  • shutdownNow:不等待直接關(guān)閉線程池

常用工具

Executors是一個(gè)工具類沼沈,能快速創(chuàng)建實(shí)用的線程池荤牍,但是返回的ExecuteService接口缺少很多ThreadPoolExecutor的方法需要注意

Executors.newCachedThreadPool()

corePoolSize為0案腺,maximumPoolSize為整數(shù)最大值,keepAliveTime為60秒康吵,隊(duì)列為SynchronousQueue

創(chuàng)建一個(gè)可緩存線程池劈榨,如果有空閑線程則交給新任務(wù),否則創(chuàng)建新的線程晦嵌。

Executors.newFixedThreadPool()

corePoolSize同辣,maximumPoolSize自定義,keepAliveTime為0秒惭载,隊(duì)列為LinkedBlockingQueue

創(chuàng)建一個(gè)定長線程池旱函,可控制線程最大并發(fā)數(shù),超出的線程會在隊(duì)列中等待描滔。

Executors.newScheduledThreadPool()

corePoolSize自定義棒妨,maximumPoolSize為整數(shù)最大值,keepAliveTime為0秒含长,隊(duì)列為DelayedWorkQueue

創(chuàng)建一個(gè)定長線程池券腔,支持定時(shí)及周期性任務(wù)執(zhí)行。

Executors.newSingleThreadExecutor()

corePoolSize拘泞,maximumPoolSize為1纷纫,keepAliveTime為0秒,隊(duì)列為LinkedBlockingQueue

創(chuàng)建一個(gè)單線程化的線程池陪腌,它只會用唯一的工作線程來執(zhí)行任務(wù)辱魁,保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級)執(zhí)行

例子

<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>

public class ThreadPoolTest {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            executorService.execute(() -> System.out.println(finalI));
        }
        executorService.shutdown();
    }
}

以上代碼將非順序輸出09,類似于fixed诗鸭,但single的將順序輸出09

<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>

public class ThreadPoolTest {
    public static void main(String[] args) {
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
//        executorService.schedule(() -> System.out.println("hehe"), 1, TimeUnit.SECONDS);
        executorService.scheduleAtFixedRate(() -> System.out.println("hehe"), 1, 2, TimeUnit.SECONDS);
//        executorService.shutdown();
    }
}

以上代碼是newScheduledThreadPool的典型使用方式染簇,將按照計(jì)劃的方式來執(zhí)行任務(wù)

配置線程池的建議

  • CPU密集型任務(wù):CPU數(shù) + 1
  • IO密集型任務(wù):CPU數(shù) * 2

先將線程池大小設(shè)置為參考值,再觀察任務(wù)運(yùn)行情況和系統(tǒng)負(fù)載强岸、資源利用率來進(jìn)行適當(dāng)調(diào)整剖笙。

PS:給大家推薦一個(gè)java資源共享,學(xué)習(xí)交流群《957734884》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末请唱,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子过蹂,更是在濱河造成了極大的恐慌十绑,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件酷勺,死亡現(xiàn)場離奇詭異本橙,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)脆诉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進(jìn)店門刃跛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來柱告,“玉大人简烘,你說我怎么就攤上這事∫畚疲” “怎么了?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵暇唾,是天一觀的道長促脉。 經(jīng)常有香客問我,道長策州,這世上最難降的妖魔是什么瘸味? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮够挂,結(jié)果婚禮上旁仿,老公的妹妹穿的比我還像新娘。我一直安慰自己孽糖,他們只是感情好枯冈,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著梭姓,像睡著了一般霜幼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上誉尖,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天罪既,我揣著相機(jī)與錄音,去河邊找鬼铡恕。 笑死琢感,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的探熔。 我是一名探鬼主播驹针,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼诀艰!你這毒婦竟也來了柬甥?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤其垄,失蹤者是張志新(化名)和其女友劉穎苛蒲,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绿满,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡臂外,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片漏健。...
    茶點(diǎn)故事閱讀 40,444評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡嚎货,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蔫浆,到底是詐尸還是另有隱情殖属,我是刑警寧澤,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布克懊,位于F島的核電站忱辅,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏谭溉。R本人自食惡果不足惜墙懂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望扮念。 院中可真熱鬧损搬,春花似錦、人聲如沸柜与。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽弄匕。三九已至颅悉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間迁匠,已是汗流浹背剩瓶。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留城丧,地道東北人延曙。 一個(gè)月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像亡哄,于是被迫代替她去往敵國和親枝缔。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,455評論 2 359

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