Java線程池詳解 — 如何使用線程池碎税,及其注意事項與調(diào)優(yōu)!

服務(wù)端應(yīng)用程序(如數(shù)據(jù)庫和 Web 服務(wù)器)需要處理來自客戶端的高并發(fā)馏锡、耗時較短的請求任務(wù)雷蹂,所以頻繁的創(chuàng)建處理這些請求的所需要的線程就是一個非常消耗資源的操作。

常規(guī)的方法是針對一個新的請求創(chuàng)建一個新線程杯道,雖然這種方法似乎易于實現(xiàn)匪煌,但它有重大缺點。為每個請求創(chuàng)建新線程將花費更多的時間,在創(chuàng)建和銷毀線程時花費更多的系統(tǒng)資源萎庭。因此同時創(chuàng)建太多線程的 JVM 可能會導(dǎo)致系統(tǒng)內(nèi)存不足霜医,這就需要限制要創(chuàng)建的線程數(shù),也就是需要使用到線程池驳规。

記得點贊收藏加關(guān)注哦 肴敛,需要下載PDF版本和獲取更多知識點、面試題的朋友可以加q群:580763979? ?備注:簡書? ?免費領(lǐng)取~

一达舒、什么是 Java 中的線程池值朋?

線程池技術(shù)就是線程的重用技術(shù)叹侄,使用之前創(chuàng)建好的線程來執(zhí)行當(dāng)前任務(wù)巩搏,并提供了針對線程周期開銷和資源沖突問題的解決方案。 由于請求到達時線程已經(jīng)存在趾代,因此消除了線程創(chuàng)建過程導(dǎo)致的延遲贯底,使應(yīng)用程序得到更快的響應(yīng)。

Java提供了以Executor接口及其子接口ExecutorService和ThreadPoolExecutor為中心的執(zhí)行器框架撒强。通過使用Executor禽捆,完成線程任務(wù)只需實現(xiàn) Runnable接口并將其交給執(zhí)行器執(zhí)行即可。

為您封裝好線程池飘哨,將您的編程任務(wù)側(cè)重于具體任務(wù)的實現(xiàn)胚想,而不是線程的實現(xiàn)機制。

若要使用線程池芽隆,我們首先創(chuàng)建一個 ExecutorService對象浊服,然后向其傳遞一組任務(wù)。ThreadPoolExcutor 類則可以設(shè)置線程池初始化和最大的線程容量胚吁。

上圖表示線程池初始化具有3 個線程牙躺,任務(wù)隊列中有5 個待運行的任務(wù)對象。

執(zhí)行器線程池方法

在固定線程池的情況下腕扶,如果執(zhí)行器當(dāng)前運行的所有線程孽拷,則掛起的任務(wù)將放在隊列中,并在線程變?yōu)榭臻e時執(zhí)行半抱。

二脓恕、線程池示例

在下面的內(nèi)容中,我們將介紹線程池的executor執(zhí)行器窿侈。

創(chuàng)建線程池處理任務(wù)要遵循的步驟

創(chuàng)建一個任務(wù)對象(實現(xiàn)Runnable接口)进肯,用于執(zhí)行具體的任務(wù)邏輯

使用Executors創(chuàng)建線程池ExecutorService

將待執(zhí)行的任務(wù)對象交給ExecutorService進行任務(wù)處理

停掉 Executor 線程池

//第一步: 創(chuàng)建一個任務(wù)對象(實現(xiàn)Runnable接口),用于執(zhí)行具體的任務(wù)邏輯 (Step 1)

class Task implements Runnable? {

? ? private String name;

? ? public Task(String s) {

? ? ? ? name = s;

? ? }

? ? // 打印任務(wù)名稱并Sleep 1秒

? ? // 整個處理流程執(zhí)行5次

? ? public void run() {

? ? ? ? try{

? ? ? ? ? ? for (int i = 0; i<=5; i++) {

? ? ? ? ? ? ? ? if (i==0) {

? ? ? ? ? ? ? ? ? ? Date d = new Date();

? ? ? ? ? ? ? ? ? ? SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss");

? ? ? ? ? ? ? ? ? ? System.out.println("任務(wù)初始化" + name +" = " + ft.format(d));

? ? ? ? ? ? ? ? ? ? //第一次執(zhí)行的時候棉磨,打印每一個任務(wù)的名稱及初始化的時間

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? else{

? ? ? ? ? ? ? ? ? ? Date d = new Date();

? ? ? ? ? ? ? ? ? ? SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss");

? ? ? ? ? ? ? ? ? ? System.out.println("任務(wù)正在執(zhí)行" + name +" = " + ft.format(d));

? ? ? ? ? ? ? ? ? ? // 打印每一個任務(wù)處理的執(zhí)行時間

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? Thread.sleep(1000);

? ? ? ? ? ? }

? ? ? ? ? ? System.out.println("任務(wù)執(zhí)行完成" + name);

? ? ? ? }? catch(InterruptedException e)? {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? }

? ? }

}

測試用例

public class ThreadPoolTest {

? ? // 線程池里面最大線程數(shù)量

? ? static final int MAX_SIZE = 3;

? ? public static void main (String[] args) {

? ? ? ? // 創(chuàng)建5個任務(wù)

? ? ? ? Runnable r1 = new Task("task 1");

? ? ? ? Runnable r2 = new Task("task 2");

? ? ? ? Runnable r3 = new Task("task 3");

? ? ? ? Runnable r4 = new Task("task 4");

? ? ? ? Runnable r5 = new Task("task 5");

? ? ? ? // 第二步:創(chuàng)建一個固定線程數(shù)量的線程池江掩,線程數(shù)為MAX_SIZE

? ? ? ? ExecutorService pool = Executors.newFixedThreadPool(MAX_SIZE);

? ? ? ? // 第三步:將待執(zhí)行的任務(wù)對象交給ExecutorService進行任務(wù)處理

? ? ? ? pool.execute(r1);

? ? ? ? pool.execute(r2);

? ? ? ? pool.execute(r3);

? ? ? ? pool.execute(r4);

? ? ? ? pool.execute(r5);

? ? ? ? // 第四步:關(guān)閉線程池

? ? ? ? pool.shutdown();

? ? }

}

示例執(zhí)行結(jié)果

任務(wù)初始化task 1 = 05:25:55

任務(wù)初始化task 2 = 05:25:55

任務(wù)初始化task 3 = 05:25:55

任務(wù)正在執(zhí)行task 3 = 05:25:56

任務(wù)正在執(zhí)行task 1 = 05:25:56

任務(wù)正在執(zhí)行task 2 = 05:25:56

任務(wù)正在執(zhí)行task 1 = 05:25:57

任務(wù)正在執(zhí)行task 3 = 05:25:57

任務(wù)正在執(zhí)行task 2 = 05:25:57

任務(wù)正在執(zhí)行task 3 = 05:25:58

任務(wù)正在執(zhí)行task 1 = 05:25:58

任務(wù)正在執(zhí)行task 2 = 05:25:58

任務(wù)正在執(zhí)行task 2 = 05:25:59

任務(wù)正在執(zhí)行task 3 = 05:25:59

任務(wù)正在執(zhí)行task 1 = 05:25:59

任務(wù)正在執(zhí)行task 1 = 05:26:00

任務(wù)正在執(zhí)行task 2 = 05:26:00

任務(wù)正在執(zhí)行task 3 = 05:26:00

任務(wù)執(zhí)行完成task 3

任務(wù)執(zhí)行完成task 2

任務(wù)執(zhí)行完成task 1

任務(wù)初始化task 5 = 05:26:01

任務(wù)初始化task 4 = 05:26:01

任務(wù)正在執(zhí)行task 4 = 05:26:02

任務(wù)正在執(zhí)行task 5 = 05:26:02

任務(wù)正在執(zhí)行task 4 = 05:26:03

任務(wù)正在執(zhí)行task 5 = 05:26:03

任務(wù)正在執(zhí)行task 5 = 05:26:04

任務(wù)正在執(zhí)行task 4 = 05:26:04

任務(wù)正在執(zhí)行task 4 = 05:26:05

任務(wù)正在執(zhí)行task 5 = 05:26:05

任務(wù)正在執(zhí)行task 4 = 05:26:06

任務(wù)正在執(zhí)行task 5 = 05:26:06

任務(wù)執(zhí)行完成task 4

任務(wù)執(zhí)行完成task 5


如程序執(zhí)行結(jié)果中顯示的一樣,任務(wù) 4 或任務(wù) 5 僅在池中的線程變?yōu)榭臻e時才執(zhí)行。在此之前环形,額外的任務(wù)將放在待執(zhí)行的隊列中策泣。

線程池執(zhí)行前三個任務(wù),線程池內(nèi)線程回收空出來之后再去處理執(zhí)行任務(wù) 4 和 5

使用這種線程池方法的一個主要優(yōu)點是抬吟,假如您希望一次處理10000個請求萨咕,但不希望創(chuàng)建10000個線程,從而避免造成系統(tǒng)資源的過量使用導(dǎo)致的宕機火本。您可以使用此方法創(chuàng)建一個包含500個線程的線程池危队,并且可以向該線程池提交500個請求。

ThreadPool此時將創(chuàng)建最多500個線程钙畔,一次處理500個請求茫陆。在任何一個線程的進程完成之后,ThreadPool將在內(nèi)部將第501個請求分配給該線程擎析,并將繼續(xù)對所有剩余的請求執(zhí)行相同的操作簿盅。在系統(tǒng)資源比較緊張的情況下,線程池是保證程序穩(wěn)定運行的一個有效的解決方案揍魂。

三桨醋、使用線程池的注意事項與調(diào)優(yōu)

1.死鎖 :?雖然死鎖可能發(fā)生在任何多線程程序中,但線程池引入了另一個死鎖案例现斋,其中所有執(zhí)行線程都在等待隊列中某個阻塞線程的執(zhí)行結(jié)果喜最,導(dǎo)致線程無法繼續(xù)執(zhí)行。

2.線程泄漏 :?如果線程池中線程在任務(wù)完成時未正確返回庄蹋,將發(fā)生線程泄漏問題瞬内。例如,某個線程引發(fā)異常并且池類沒有捕獲此異常蔓肯,則線程將異常退出遂鹊,從而線程池的大小將減小一個。如果這種情況重復(fù)多次蔗包,則線程池最終將變?yōu)榭毡耍瑳]有線程可用于執(zhí)行其他任務(wù)。

3.線程頻繁輪換:?如果線程池大小非常大调限,則線程之間進行上下文切換會浪費很多時間舟陆。所以在系統(tǒng)資源允許的情況下,也不是線程池越大越好耻矮。

線程池大小優(yōu)化:?線程池的最佳大小取決于可用的處理器數(shù)量和待處理任務(wù)的性質(zhì)秦躯。對于CPU密集型任務(wù),假設(shè)系統(tǒng)有N個邏輯處理核心裆装,N 或 N+1 的最大線程池數(shù)量大小將實現(xiàn)最大效率踱承。對于 I/O密集型任務(wù)倡缠,需要考慮請求的等待時間(W)和服務(wù)處理時間(S)的比例,線程池最大大小為 N*(1+ W/S)會實現(xiàn)最高效率茎活。

不要教條的使用上面的總結(jié)昙沦,需要根據(jù)自己的應(yīng)用任務(wù)處理類型進行靈活的設(shè)置與調(diào)優(yōu),其中少不了測試實驗载荔。

總結(jié)

我這里也準備了一線大廠面試資料和超硬核PDF技術(shù)文檔盾饮,以及我為大家精心準備的多套簡歷模板(不斷更新中),希望大家都能找到心儀的工作懒熙!

有需要的朋友可以加q群:580763979? ?備注:簡書? ?免費領(lǐng)取~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末丘损,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子工扎,更是在濱河造成了極大的恐慌徘钥,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件定庵,死亡現(xiàn)場離奇詭異吏饿,居然都是意外死亡踪危,警方通過查閱死者的電腦和手機蔬浙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贞远,“玉大人畴博,你說我怎么就攤上這事±吨伲” “怎么了俱病?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長袱结。 經(jīng)常有香客問我亮隙,道長,這世上最難降的妖魔是什么垢夹? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任溢吻,我火速辦了婚禮,結(jié)果婚禮上果元,老公的妹妹穿的比我還像新娘促王。我一直安慰自己,他們只是感情好而晒,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布蝇狼。 她就那樣靜靜地躺著,像睡著了一般倡怎。 火紅的嫁衣襯著肌膚如雪迅耘。 梳的紋絲不亂的頭發(fā)上贱枣,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機與錄音颤专,去河邊找鬼冯事。 笑死,一個胖子當(dāng)著我的面吹牛血公,可吹牛的內(nèi)容都是我干的昵仅。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼累魔,長吁一口氣:“原來是場噩夢啊……” “哼摔笤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起垦写,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤吕世,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后梯投,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體命辖,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年分蓖,在試婚紗的時候發(fā)現(xiàn)自己被綠了尔艇。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡么鹤,死狀恐怖终娃,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蒸甜,我是刑警寧澤棠耕,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站柠新,受9級特大地震影響窍荧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜恨憎,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一蕊退、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧框咙,春花似錦咕痛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至者铜,卻和暖如春腔丧,著一層夾襖步出監(jiān)牢的瞬間放椰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工愉粤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留砾医,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓衣厘,卻偏偏與公主長得像如蚜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子影暴,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345

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