Java線程池 不看後悔

ThreadPoolExecutor使用

提示:若有疑問請私信聯繫、下方有源代碼地址,請自行拿取git


前言

ThreadPoolExecutor是JDK1.5以後纔有的線程池類,JDK幫咱們實現了基於ThreadPoolExecutor建立的newSingleThreadExecutor、newFixedThreadPool、newCachedThreadPool等方便使用的線程池,那麼爲何這些線程池在阿里巴巴的開發規範中卻不推薦使用呢? 我相信讀了這篇文章後你將豁然開朗。

提示:如下是本篇文章正文內容,下面案例可供參考markdown

1、技術介紹

1.線程池是什麼?

線程池是一種多線程處理形式,處理過程當中將任務添加到隊列,而後在建立線程後自動啓動這些任務。線程池線程都是後臺線程。每一個線程都使用默認的堆棧大小,以默認的優先級運行,並處於多線程單元中。若是某個線程在託管代碼中空閒(如正在等待某個事件),則線程池將插入另外一個輔助線程來使全部處理器保持繁忙。若是全部線程池線程都始終保持繁忙,但隊列中包含掛起的工做,則線程池將在一段時間後建立另外一個輔助線程但線程的數目永遠不會超過最大值。超過最大值的線程能夠排隊,但他們要等到其餘線程完成後才啓動。 ---摘自百度百科

2、使用步驟

1.ThreadPoolExecutor參數介紹

參數名稱 參數類型 參數含義
corePoolSize int 核心線程池大小
maximumPoolSize int 最大線程池大小
keepAliveTime long 線程最大空閒時間
unit TimeUnit 時間單位
workQueue BlockingQueue 線程等待隊列
threadFactory ThreadFactory 線程建立工廠
handler RejectedExecutionHandler 拒絕策略
咱們看下ThreadPoolExecutor類的execute方法底層源碼進行分析
在這裏插入圖片描述
OK,根據判斷可知:

1.若是正在運行的線程少於corePoolSize線程,請嘗試使用給定命令做爲其第一個任務啓動一個新線程。多線程

2.若是任務能夠成功排隊,那麼咱們仍然須要再次檢查是否應該添加線程(由於現有線程自上次檢查後就死掉了),或者自進入此方法後該池已關閉。所以,咱們從新檢查狀態,並在必要時回滾排隊,若是中止,或者若是沒有線程,則啓動一個新線程。併發

3.若是咱們沒法將任務排隊,則嘗試添加一個新線程。若是失敗,咱們知道咱們已關閉或處於飽和狀態,所以拒絕該任務。函數

2.newSingleThreadExecutor使用

代碼以下(示例):高併發

@Test public void testNewSingleThreadExecutor() {
        ExecutorService threaPool = Executors.newSingleThreadExecutor();
        long start = System.currentTimeMillis();
        System.out.println("線程池執行開始");
        int idx = 10;
        while (--idx > 0) {
            threaPool.execute(() -> {
                try {
                    LOGGER.info("線程執行中");
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException interruptedException) {
                    interruptedException.printStackTrace();
                }
            });
        }
        threaPool.shutdown();
        for (; ; ) {
            if (threaPool.isTerminated())
                break;
        }
        long end = System.currentTimeMillis();
        System.out.println("線程池執行結束,總用時:" + (end - start) + " ms ");


    }

複製代碼

此測試方法運行的結果以下: 注意看我用紅框標記的地方,只採用了1個線程去執行,原理是什麼呢?讓咱們看看newSingleThreadExecutor的源碼oop

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

複製代碼

構建了ThreadPoolExecutor線程池,核心線程1個,最大執行線程1個,等待隊列是LinkedBlockingQueue,我們再點進去看看LinkedBlockingQueue默認構造函數是啥 在這裏插入圖片描述 在這裏插入圖片描述 能夠看到這是默認時一個容量爲Interger.MAX_VALUE的隊列源碼分析

結論:newSingleThreadExecutor是一個核心線程爲1,線程池中容許最大線程爲1,等待隊列爲無限大的線程池,因此你應該知道爲何它只開了一個線程去執行了。單元測試

3.newFixedThreadPool使用

代碼以下(示例):測試

@Test public void testNewFixedThreadPool() {
      	ExecutorService threaPool = Executors.newFixedThreadPool(5);
        long start = System.currentTimeMillis();
        System.out.println("線程池執行開始");
        int idx = 20;
        while (--idx >= 0) {
            threaPool.execute(() -> {
                try {
                    LOGGER.info("線程執行中");
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException interruptedException) {
                    interruptedException.printStackTrace();
                }
            });
        }
        threaPool.shutdown();
        for (; ; ) {
            if (threaPool.isTerminated())
                break;
        }
        long end = System.currentTimeMillis();
        System.out.println("線程池執行結束,總用時:" + (end - start) + " ms ");
    }

複製代碼

先來看下執行結果

在這裏插入圖片描述 OK,看下執行結果可知,只開啓了5個線程,每次批量的執行5個,接下來我們看看它的源碼 在這裏插入圖片描述 也一樣的構造了ThreadPoolExecutor線程池,參數爲:核心線程數、線程池最大線程數都爲傳入的參數,單元測試傳的是5,因此開5個線程運行,運行完重複使用這5個線程去執行隊列中的。

結論:newFixedThreadPool是一個根據傳入參數來執行固定大小的線程池

4.newCachedThreadPool使用

代碼以下(示例):

@Test public void testNewCachedThreadPool() {
        ExecutorService threaPool = Executors.newCachedThreadPool();
        long start = System.currentTimeMillis();
        System.out.println("線程池執行開始");
        int idx = 200;
        while (--idx >= 0) {
            threaPool.execute(() -> {
                    LOGGER.info("線程執行中");

            });
        }
        threaPool.shutdown();
        for (; ; ) {
            if (threaPool.isTerminated())
                break;
        }
        long end = System.currentTimeMillis();
        System.out.println("線程池執行結束,總用時:" + (end - start) + " ms ");
    }

複製代碼

OK,這裏跟上面不一樣,我們執行200個線程,咋們先看執行結果, 在這裏插入圖片描述 很明顯能夠看到跟上面的不一樣,在執行時間很短的任務時重複的利用線程去執行,緣由是什麼呢?我們先看源碼 在這裏插入圖片描述 建立了一個核心線程數爲0,最大執行線程爲Interger.MAX_VALUE,而且注意這裏用了SynchronousQueue這個隊列,SynchronousQueue沒有容量,是無緩衝等待隊列,是一個不存儲元素的阻塞隊列,會直接將任務交給消費者,必須等隊列中的添加元素被消費後才能繼續添加新的元素。

SynchronousQueue,至於它的底層原理後期會寫一篇專門關於隊列的文章,這裏再也不細說

結論:newCachedThreadPool它是一個能夠無限擴大的線程池,當前沒有空閒線程時它會建立一個新的線程,若是有空閒線程會使用空閒線程處理

5.線程池的使用推薦

經過以上的測試案例與源碼分析,相信你們對線程池有了必定的認識,總結以下:

1.newSingleThreadExecutor:只開啓一個線程運行,處理效率較慢,阻塞隊列大小是沒有大小限制的,若是隊列堆積數據太多會形成資源消耗

2.newFixedThreadPool:一個固定大小的線程池,可控制線程併發數量,但阻塞隊列大小是沒有大小限制的,若是隊列堆積數據太多會形成資源消耗

3.newCachedThreadPool:比較適合處理執行時間較短的業務,但線程如果無限制的建立,可能會致使內存佔用過多而產生OOM,而且會形成cpu過分切換消耗太多資源。

因此使用推薦是根據業務場景實現自定義ThreadPoolExecutor,特別是高併發大流量系統,這也是爲何阿里內部不推薦使用以上幾種線程池的緣由。

做者寄語

是否是感受很簡單?更多用法請點擊下方查看源碼,關注我帶你揭祕更多高級用法

源碼地址:點此查看源碼.

相關文章
相關標籤/搜索