JavaGuide知識點整理——線程池的最佳實踐

重新聲明下吃引,雖然開這系列筆記的時候就說了這是最近看javaguide網站,然后為了加深記憶也為了好知識一起分享滓侍,所以把網站中的知識點搬運了一遍顶伞,其中摻雜這我自己的理解和實踐等喝峦。然后各位如果感覺去可以去看原文势誊。附上鏈接:https://snailclimb.gitee.io/javaguide/#/./docs/java/concurrent/java-thread-pool-best-practices

線程池在實際項目中的使用場景

線程池一般用于執(zhí)行多個不相關聯(lián)的耗時任務。沒有多線程的情況下谣蠢,任務使用順序執(zhí)行粟耻,使用了線程池的話可以讓多個不相關聯(lián)的任務同時執(zhí)行。

假設我們要執(zhí)行三個不相關的耗時任務眉踱。使用線程池前后的區(qū)別如下:


使用多線程前后

注意這里使用多線程執(zhí)行不同的任務挤忙,可以用一個countDownLatch等待子任務執(zhí)行完成才繼續(xù)往下返回結果。

線程池最佳實踐

使用ThreadPoolExecutor的構造函數(shù)聲明線程池

線程池必須手動通過ThreadPoolExecutor的構造函數(shù)聲明谈喳,避免使用Executors類的newFixedThreadPool和newCachedThreadPool册烈,因為可能會有oom的風險。
說白了就是使用有界隊列婿禽,控制線程創(chuàng)建數(shù)量茄厘。
除了避免OOM的原因外,不推薦使用Executors提佛那個的快捷線程池還有兩個原因:

  1. 實際使用中需要根據(jù)自己機器的性能谈宛,業(yè)務場景來手動配置線程池的參數(shù)。比如核心線程數(shù)胎署,最大線程數(shù)吆录,使用的任務隊列,拒絕策略等琼牧。
  2. 我們應該顯示的給我們的線程池命名恢筝,這樣有助于我們定位問題。

監(jiān)測線程池運行狀態(tài)

我們可以通過一些手段來檢測線程池的運行狀態(tài)巨坊,比如SpringBoot中的Actuator組件撬槽。
除此之外我們還可以通過ThreadPoolExecutor的相關api做一個簡陋的監(jiān)控。從下圖可以看出趾撵,ThreadPoolExecutor提供了互毆線程池當前的線程數(shù)和活躍線程數(shù)侄柔,已執(zhí)行完成的任務數(shù),正在排隊的任務數(shù)等任務占调。


ThreadPoolExecutor獲取當前線程池信息

建議不同類別的業(yè)務用不同的線程池

很多人在實際項目中會有類似這樣的問題:我的項目中多個業(yè)務需要用到線程池暂题,是為每個線程池都定義一個還是說定義一個公共的線程池呢?
一般建議不同的業(yè)務使用不同的線程池究珊, 配置線程池的時候根據(jù)當前業(yè)務情況對當前線程池進行配置薪者,因為不同的業(yè)務的并發(fā)以及對資源的使用情況都不同,重新優(yōu)化系統(tǒng)性能瓶頸相關的業(yè)務剿涮。

下面我們看一個線程池運用不當?shù)木€上事故案例:


線程池使用不當demo

圖中的代碼可能會存在死鎖的情況言津。為什么呢攻人?下面我們捋一捋。
試想這么一個極端現(xiàn)象:如果核心線程數(shù)是n悬槽,父任務數(shù)量也為n怀吻。把核心線程全部占用。然后父任務下的子任務也需要用線程陷谱。在任務隊列中阻塞等待獲取線程烙博。而父任務在等待子任務執(zhí)行完成,子任務等待父任務釋放線程資源好獲取線程烟逊。也就造成了死鎖渣窜。

解決方法也很簡單,新增加一個用于執(zhí)行子任務的線程池專門為其服務宪躯。

要給線程池命名

初始化線程池的時候需要顯示命名(設置線程池名稱前綴)乔宿,有利于定位問題。
默認情況下創(chuàng)建的線程名字類似pool-1-thread-n這樣的访雪,沒有業(yè)務含義详瑞,不利于我們定位問題。
給線程池里的線程命名通常有兩種方式:

  1. 利用谷歌的ThreadFactoryBuilder給線程池里的線程命名
    public static void main(String[] args) throws Exception {
        ThreadFactory threadFactory =
            new ThreadFactoryBuilder().setNameFormat("用來測似的線程池" + "-%d").setDaemon(true).build();
       ThreadPoolExecutor threadPoolExecutor =
           new ThreadPoolExecutor(2,10,1l,TimeUnit.SECONDS,new ArrayBlockingQueue<>(10),threadFactory);
       for(int i = 0;i<10;i++){
           threadPoolExecutor.execute(()->{
               System.out.println("當前線程名:"+Thread.currentThread().getName());
           });
       }
    }

如上代碼就是命名了臣缀,下面是如果這個線程池里的線程報錯坝橡,可以很容易定位。


定位到問題出自這個線程池
  1. 也可以自己實現(xiàn)ThreadFactor來給線程池里的線程命名
public class Test {

    public static void main(String[] args) throws Exception {
        MyThreadFactory threadFactory = new MyThreadFactory(Executors.defaultThreadFactory(),"測試線程池");
       ThreadPoolExecutor threadPoolExecutor =
           new ThreadPoolExecutor(2,10,1l,TimeUnit.SECONDS,new ArrayBlockingQueue<>(10),threadFactory);
       for(int i = 0;i<10;i++){
           threadPoolExecutor.execute(()->{
               System.out.println("當前線程名:"+Thread.currentThread().getName());
           });
       }
    }
}

final  class MyThreadFactory implements ThreadFactory{

    ThreadFactory threadFactory;
    String name;
    AtomicInteger i = new AtomicInteger(1);

    MyThreadFactory(ThreadFactory threadFactory,String name){
        this.threadFactory = threadFactory;
        this.name = name;
    }

    @Override
    public Thread newThread(Runnable r) {
        Thread t = threadFactory.newThread(r);
        t.setName(name+i.getAndIncrement());
        return t;
    }
}

其實這種寫法就是單純的包了一層精置,每一個線程都手動的setName給設置了個名字计寇。具體要用那種寫法都可以,反正我是覺得自己設置的這個最開始寫要麻煩點脂倦,但是每次用方便番宁。谷歌的方法不需要創(chuàng)建什么工具類,但是每次創(chuàng)建都要設置赖阻。

正確的配置線程池參數(shù)

常規(guī)操作
首先這里要說一個常識:并不是線程越多越好蝶押。比如一個很小的任務,一個人做要1小時火欧,但是60個人也不會是一分鐘棋电。甚至因為人多交流成本太大。指不定還會用時更長布隔。
線程數(shù)量過多的影響也是和我們分配多少人做事一樣离陶。對于多線程的場景主要是增加了上下文切換成本。

類比我們人類通過合作做某件事衅檀,我們可以知道線程池過大過小都不好招刨,合適才是最好的。
如果我們設置的線程池數(shù)量太小哀军,同一時間有大量任務/請求需要處理沉眶,可能會導致大量的請求/任務在任務隊列中排隊等待執(zhí)行打却,甚至出現(xiàn)隊列滿了之后任務/請求無法處理的情況』丫螅或者大量任務堆積在隊列中導致OOM柳击,這樣很明顯是有問題的。CPU根本沒有得到充分利用片习。
但是我們設置線程數(shù)量過大捌肴,大量線程可能同時爭奪CPU資源,這樣會導致大量的上下文切換藕咏,從而增加線程的執(zhí)行時間状知,影響整體執(zhí)行效率。

網上有一個簡單并且通用的公式:

  • CPU密集型任務(n+1):這種任務消耗的是CPU資源孽查,可以將線程數(shù)設置為cpu核心數(shù)+1.比CPU核心數(shù)多一個是為了防止線程偶發(fā)的缺頁中斷或者其他原因導致的任務暫停饥悴。一旦任務暫停,cpu就會處于空閑狀態(tài)盲再,這個時候多出的一個線程就可以充分利用CPU時間西设。
  • I/O密集型任務(2n):這種任務應用起來系統(tǒng)會占用大部分時間處理I/O交互,而線程處理I/O的時間段不會占用CPU來處理答朋,這時候就可以把CPU交出給其他線程使用贷揽。所以我們可以多配置一些線程。比如2N梦碗。

如何判斷是CPU密集任務還是IO密集任務擒滑?

CPU密集型簡單理解就是利用CPU計算能力的任務。比如在內存中對大量數(shù)據(jù)進行排序叉弦。但凡涉及到網絡讀取,文件讀取這類都是IO密集型藻糖。這類任務的特點是CPU計算消耗時間相比于等待IO操作完成的時間來說很少淹冰。大部分時間都花費在等待IO操作完成上。

美團的騷操作

美團技術團隊在java線程池實現(xiàn)原理以及在內圖案業(yè)務中的實踐這篇文章中介紹到對線程池參數(shù)實現(xiàn)可自定義配置的思路和方法巨柒。

美團技術團隊的思路是主要對線程池的核心參數(shù)實現(xiàn)自定義可配置樱拴。這三個核心參數(shù)是:

  • corPoolSize:核心線程數(shù)定義了最小可以同時運行的線程數(shù)量。
  • maximumPoolSize:當隊列中存放的任務達到隊列容量時洋满,當前可以同事運行的線程數(shù)量變?yōu)樽畲缶€程數(shù)晶乔。
  • workQueue:當新任務來的時候會先判斷當前運行的線程數(shù)量是否達到核心線程數(shù),如果達到的話新任務會被存放在隊列中牺勾。
  • 還包括一些隊列長度等正罢。
動態(tài)修改線程池參數(shù)

實現(xiàn)的重點是基于ThreadPoolExecutor的幾個方法,我們只需要維護ThreadPoolExecutor的實例驻民,并在需要修改的時候拿到實例修改其參數(shù)即可翻具。基于這個原理我們做到線程池參數(shù)的動態(tài)化裆泳,可視并且可配置叹洲。效果如下圖工禾。


動態(tài)修改線程池參數(shù)

其實我們基于這個思想可以做的就更多了。比如一些監(jiān)控:線程池活躍度闻葵,告警,執(zhí)行任務的頻率和耗時笙隙,Reject異常等等。從而避免故障或者加速故障的修復竟痰。感覺美團技術團隊對于線程池的實踐介紹比較淺顯易懂,感興趣的可以自己去看下坏快。

本篇筆記就記到這里铅檩,如果稍微幫到你了記得點個喜歡點個關注。也祝大家工作順順利利莽鸿,生活健健康康~昧旨!

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市祥得,隨后出現(xiàn)的幾起案子兔沃,更是在濱河造成了極大的恐慌,老刑警劉巖级及,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件乒疏,死亡現(xiàn)場離奇詭異,居然都是意外死亡饮焦,警方通過查閱死者的電腦和手機怕吴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來县踢,“玉大人转绷,你說我怎么就攤上這事∨鹌。” “怎么了议经?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我爸业,道長其骄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任扯旷,我火速辦了婚禮拯爽,結果婚禮上,老公的妹妹穿的比我還像新娘钧忽。我一直安慰自己毯炮,他們只是感情好,可當我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布耸黑。 她就那樣靜靜地躺著桃煎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪大刊。 梳的紋絲不亂的頭發(fā)上为迈,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天,我揣著相機與錄音缺菌,去河邊找鬼葫辐。 笑死,一個胖子當著我的面吹牛伴郁,可吹牛的內容都是我干的耿战。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼焊傅,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鸭栖?” 一聲冷哼從身側響起握巢,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤镜粤,失蹤者是張志新(化名)和其女友劉穎肉渴,沒想到半個月后带射,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡绪钥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年程腹,在試婚紗的時候發(fā)現(xiàn)自己被綠了儒拂。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡见转,死狀恐怖斩箫,靈堂內的尸體忽然破棺而出撵儿,到底是詐尸還是另有隱情,我是刑警寧澤统倒,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站耸成,受9級特大地震影響,放射性物質發(fā)生泄漏井氢。R本人自食惡果不足惜花竞,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一掸哑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧厌蔽,春花似錦摔癣、人聲如沸纬向。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽师脂。三九已至危彩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間汤徽,已是汗流浹背灸撰。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工浮毯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人债蓝。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓饰迹,卻偏偏與公主長得像,于是被迫代替她去往敵國和親锹淌。 傳聞我的和親對象是個殘疾皇子赠制,可洞房花燭夜當晚...
    茶點故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內容