線程池

什么是線程池?

Java中的線程池是運用場景最多的并發(fā)框架,幾乎所有需要異步或并發(fā)執(zhí)行任務(wù)的程序
都可以使用線程池。在開發(fā)過程中,合理地使用線程池能夠帶來3個好處。
第一:降低資源消耗。通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗。
第二:提高響應(yīng)速度。當任務(wù)到達時,任務(wù)可以不需要等到線程創(chuàng)建就能立即執(zhí)行。
第三:提高線程的可管理性。線程是稀缺資源,如果無限制地創(chuàng)建,不僅會消耗系統(tǒng)資源,
還會降低系統(tǒng)的穩(wěn)定性,使用線程池可以進行統(tǒng)一分配、調(diào)優(yōu)和監(jiān)控。但是,要做到合理利用
線程池,必須對其實現(xiàn)原理了如指掌。

image.png

線程池作用

線程池是為突然大量爆發(fā)的線程設(shè)計的,通過有限的幾個固定線程為大量的操作服務(wù),減少了創(chuàng)建和銷毀線程所需的時間,從而提高效率。
如果一個線程的時間非常長,就沒必要用線程池了(不是不能作長時間操作,而是不宜。),況且我們還不能控制線程池中線程的開始、掛起、和中止。

線程池的分類

ThreadPoolExecutor

Java是天生就支持并發(fā)的語言,支持并發(fā)意味著多線程,線程的頻繁創(chuàng)建在高并發(fā)及大數(shù)據(jù)量是非常消耗資源的,因為java提供了線程池。在jdk1.5以前的版本中,線程池的使用是及其簡陋的,但是在JDK1.5后,有了很大的改善。JDK1.5之后加入了java.util.concurrent包,

java.util.concurrent包的加入給予開發(fā)人員開發(fā)并發(fā)程序以及解決并發(fā)問題很大的幫助。這篇文章主要介紹下并發(fā)包下的Executor接口,Executor接口雖然作為一個非常舊的接口(JDK1.5 2004年發(fā)布),但是很多程序員對于其中的一些原理還是不熟悉,因此寫這篇文章來介紹下Executor接口,同時鞏固下自己的知識。如果文章中有出現(xiàn)錯誤,歡迎大家指出。

Executor框架的最頂層實現(xiàn)是ThreadPoolExecutor類,Executors工廠類中提供的newScheduledThreadPool、newFixedThreadPool、newCachedThreadPool方法其實也只是ThreadPoolExecutor的構(gòu)造函數(shù)參數(shù)不同而已。通過傳入不同的參數(shù),就可以構(gòu)造出適用于不同應(yīng)用場景下的線程池,那么它的底層原理是怎樣實現(xiàn)的呢,這篇就來介紹下ThreadPoolExecutor線程池的運行過程。

corePoolSize: 核心池的大小。 當有任務(wù)來之后,就會創(chuàng)建一個線程去執(zhí)行任務(wù),當線程池中的線程數(shù)目達到corePoolSize后,就會把到達的任務(wù)放到緩存隊列當中

maximumPoolSize: 線程池最大線程數(shù),它表示在線程池中最多能創(chuàng)建多少個線程;
keepAliveTime: 表示線程沒有任務(wù)執(zhí)行時最多保持多久時間會終止
unit: 參數(shù)keepAliveTime的時間單位,有7種取值,在TimeUnit類中有7種靜態(tài)屬性:

線程池四種創(chuàng)建方式

Java通過Executors(jdk1.5并發(fā)包)提供四種線程池,分別為:
newCachedThreadPool創(chuàng)建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。
newFixedThreadPool 創(chuàng)建一個定長線程池,可控制線程最大并發(fā)數(shù),超出的線程會在隊列中等待。
newScheduledThreadPool 創(chuàng)建一個定長線程池,支持定時及周期性任務(wù)執(zhí)行。
newSingleThreadExecutor 創(chuàng)建一個單線程化的線程池,它只會用唯一的工作線程來執(zhí)行任務(wù),保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級)執(zhí)行。

1.newCachedThreadPool

創(chuàng)建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。示例代碼如下:

    // 無限大小線程池 jvm自動回收
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int temp = i;
            newCachedThreadPool.execute(new Runnable() {

                @Override
                public void run() {
                    try {
                        Thread.sleep(100);
                    } catch (Exception e) {
                        // TODO: handle exception
                    }
                    System.out.println(Thread.currentThread().getName() + ",i:" + temp);

                }
            });
        }

總結(jié): 線程池為無限大,當執(zhí)行第二個任務(wù)時第一個任務(wù)已經(jīng)完成,會復(fù)用執(zhí)行第一個任務(wù)的線程,而不用每次新建線程。

2.newFixedThreadPool

創(chuàng)建一個定長線程池,可控制線程最大并發(fā)數(shù),超出的線程會在隊列中等待。示例代碼如下:

ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 10; i++) {
            final int temp = i;
            newFixedThreadPool.execute(new Runnable() {

                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getId() + ",i:" + temp);

                }
            });
        }


總結(jié):因為線程池大小為3,每個任務(wù)輸出index后sleep 2秒,所以每兩秒打印3個數(shù)字。
定長線程池的大小最好根據(jù)系統(tǒng)資源進行設(shè)置。如Runtime.getRuntime().availableProcessors()

3.newScheduledThreadPool

創(chuàng)建一個定長線程池,支持定時及周期性任務(wù)執(zhí)行。延遲執(zhí)行示例代碼如下:

ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5);
        for (int i = 0; i < 10; i++) {
            final int temp = i;
            newScheduledThreadPool.schedule(new Runnable() {
                public void run() {
                    System.out.println("i:" + temp);
                }
            }, 3, TimeUnit.SECONDS);
}

表示延遲3秒執(zhí)行。

4.newSingleThreadExecutor

創(chuàng)建一個單線程化的線程池,它只會用唯一的工作線程來執(zhí)行任務(wù),保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級)執(zhí)行。示例代碼如下:

ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            newSingleThreadExecutor.execute(new Runnable() {

                @Override
                public void run() {
                    System.out.println("index:" + index);
                    try {
                        Thread.sleep(200);
                    } catch (Exception e) {
                        // TODO: handle exception
                    }
                }
            });
        }

注意: 結(jié)果依次輸出,相當于順序執(zhí)行各個任務(wù)。

線程池原理剖析

提交一個任務(wù)到線程池中,線程池的處理流程如下:
1、判斷線程池里的核心線程是否都在執(zhí)行任務(wù),如果不是(核心線程空閑或者還有核心線程沒有被創(chuàng)建)則創(chuàng)建一個新的工作線程來執(zhí)行任務(wù)。如果核心線程都在執(zhí)行任務(wù),則進入下個流程。
2、線程池判斷工作隊列是否已滿,如果工作隊列沒有滿,則將新提交的任務(wù)存儲在這個工作隊列里。如果工作隊列滿了,則進入下個流程。
3、判斷線程池里的線程是否都處于工作狀態(tài),如果沒有,則創(chuàng)建一個新的工作線程來執(zhí)行任務(wù)。如果已經(jīng)滿了,則交給飽和策略來處理這個任務(wù)。

image.png

自定義線程線程池

如果當前線程池中的線程數(shù)目小于corePoolSize,則每來一個任務(wù),就會創(chuàng)建一個線程去執(zhí)行這個任務(wù);

如果當前線程池中的線程數(shù)目>=corePoolSize,則每來一個任務(wù),會嘗試將其添加到任務(wù)緩存隊列當中,若添加成功,則該任務(wù)會等待空閑線程將其取出去執(zhí)行;若添加失?。ㄒ话銇碚f是任務(wù)緩存隊列已滿),則會嘗試創(chuàng)建新的線程去執(zhí)行這個任務(wù);
如果隊列已經(jīng)滿了,則在總線程數(shù)不大于maximumPoolSize的前提下,則創(chuàng)建新的線程
如果當前線程池中的線程數(shù)目達到maximumPoolSize,則會采取任務(wù)拒絕策略進行處理;

如果線程池中的線程數(shù)量大于 corePoolSize時,如果某線程空閑時間超過keepAliveTime,線程將被終止,直至線程池中的線程數(shù)目不大于corePoolSize;如果允許為核心池中的線程設(shè)置存活時間,那么核心池中的線程空閑時間超過keepAliveTime,線程也會被終止。

public class Test0007 {

    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3));
        for (int i = 1; i <= 6; i++) {
            TaskThred t1 = new TaskThred("任務(wù)" + i);
            executor.execute(t1);
        }
        executor.shutdown();
    }
}

class TaskThred implements Runnable {
    private String taskName;

    public TaskThred(String taskName) {
        this.taskName = taskName;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+taskName);
    }

}

合理配置線程池

CPU密集

CPU密集的意思是該任務(wù)需要大量的運算,而沒有阻塞,CPU一直全速運行。
CPU密集任務(wù)只有在真正的多核CPU上才可能得到加速(通過多線程),而在單核CPU上,無論你開幾個模擬的多線程,該任務(wù)都不可能得到加速,因為CPU總的運算能力就那些。

IO密集

IO密集型,即該任務(wù)需要大量的IO,即大量的阻塞。在單線程上運行IO密集型的任務(wù)會導(dǎo)致浪費大量的CPU運算能力浪費在等待。所以在IO密集型任務(wù)中使用多線程可以大大的加速程序運行,即時在單核CPU上,這種加速主要就是利用了被浪費掉的阻塞時間。

接著上一篇探討線程池留下的尾巴,如何合理的設(shè)置線程池大小。
要想合理的配置線程池的大小,首先得分析任務(wù)的特性,可以從以下幾個角度分析:

  1. 任務(wù)的性質(zhì):CPU密集型任務(wù)、IO密集型任務(wù)、混合型任務(wù)。
  2. 任務(wù)的優(yōu)先級:高、中、低。
  3. 任務(wù)的執(zhí)行時間:長、中、短。
  4. 任務(wù)的依賴性:是否依賴其他系統(tǒng)資源,如數(shù)據(jù)庫連接等。
    性質(zhì)不同的任務(wù)可以交給不同規(guī)模的線程池執(zhí)行。
    對于不同性質(zhì)的任務(wù)來說,CPU密集型任務(wù)應(yīng)配置盡可能小的線程,如配置CPU個數(shù)+1的線程數(shù),IO密集型任務(wù)應(yīng)配置盡可能多的線程,因為IO操作不占用CPU,不要讓CPU閑下來,應(yīng)加大線程數(shù)量,如配置兩倍CPU個數(shù)+1,而對于混合型的任務(wù),如果可以拆分,拆分成IO密集型和CPU密集型分別處理,前提是兩者運行的時間是差不多的,如果處理時間相差很大,則沒必要拆分了。
    若任務(wù)對其他系統(tǒng)資源有依賴,如某個任務(wù)依賴數(shù)據(jù)庫的連接返回的結(jié)果,這時候等待的時間越長,則CPU空閑的時間越長,那么線程數(shù)量應(yīng)設(shè)置得越大,才能更好的利用CPU。
    當然具體合理線程池值大小,需要結(jié)合系統(tǒng)實際情況,在大量的嘗試下比較才能得出,以上只是前人總結(jié)的規(guī)律。

最佳線程數(shù)目 = ((線程等待時間+線程CPU時間)/線程CPU時間 )* CPU數(shù)目
比如平均每個線程CPU運行時間為0.5s,而線程等待時間(非CPU運行時間,比如IO)為1.5s,CPU核心數(shù)為8,那么根據(jù)上面這個公式估算得到:((0.5+1.5)/0.5)8=32。這個公式進一步轉(zhuǎn)化為:
最佳線程數(shù)目 = (線程等待時間與線程CPU時間之比 + 1)
CPU數(shù)目
可以得出一個結(jié)論:
線程等待時間所占比例越高,需要越多線程。線程CPU時間所占比例越高,需要越少線程。
以上公式與之前的CPU和IO密集型任務(wù)設(shè)置線程數(shù)基本吻合。

CPU密集型時,任務(wù)可以少配置線程數(shù),大概和機器的cpu核數(shù)相當,這樣可以使得每個線程都在執(zhí)行任務(wù)
IO密集型時,大部分線程都阻塞,故需要多配置線程數(shù),2*cpu核數(shù)
操作系統(tǒng)之名稱解釋:
某些進程花費了絕大多數(shù)時間在計算上,而其他則在等待I/O上花費了大多是時間,
前者稱為計算密集型(CPU密集型)computer-bound,后者稱為I/O密集型,I/O-bound。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

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

  • Java線程池 [toc] 什么是線程池 線程池就是有N個子線程共同在運行的線程組合。 舉個容易理解的例子:有個線...
    石家志遠閱讀 1,329評論 0 6
  • 一.Java中的ThreadPoolExecutor類 java.uitl.concurrent.ThreadPo...
    誰在烽煙彼岸閱讀 654評論 0 0
  • 【摘錄】我們用不需要的東西堆滿房間,用社交網(wǎng)絡(luò)填充自己的空閑,發(fā)朋 友圈、寫博客或是發(fā)一些貓貓狗狗的視頻。雜事和雜...
    圓圓臉_1a8d閱讀 128評論 0 0
  • 讀書的時候因為無聊學(xué)了思科的ccna,接觸到以太網(wǎng),ethernet這個詞,當時沒當回事。隨后斷斷續(xù)續(xù)在各種科普讀...
    scvhuang閱讀 592評論 3 2
  • <jsp:getProperty>動作元素 jsp:getProperty動作提取指定Bean屬性的值,轉(zhuǎn)換成字符...
    FTOLsXD閱讀 342評論 0 1