天天看點

Java線程池深入淺出ThreadPoolExecutor使用前言一、技術介紹二、使用步驟作者寄語

Java線程池深入淺出

  • ThreadPoolExecutor使用
  • 前言
  • 一、技術介紹
    • 1.線程池是什麼?
  • 二、使用步驟
    • 1.ThreadPoolExecutor參數介紹
    • 2.newSingleThreadExecutor使用
    • 3.newFixedThreadPool使用
    • 4.newCachedThreadPool使用
    • 5.線程池的使用推薦
  • 作者寄語

ThreadPoolExecutor使用

提示:如有疑問請私信聯系、下方有源代碼位址,請自行拿取

前言

ThreadPoolExecutor是JDK1.5之後才有的線程池類,JDK幫我們實作了基于ThreadPoolExecutor建立的newSingleThreadExecutor、newFixedThreadPool、newCachedThreadPool等友善使用的線程池,那麼為什麼這些線程池在阿裡巴巴的開發規範中卻不推薦使用呢? 我相信讀了這篇文章後你将豁然開朗。

提示:以下是本篇文章正文内容,下面案例可供參考

一、技術介紹

1.線程池是什麼?

線程池是一種多線程處理形式,處理過程中将任務添加到隊列,然後在建立線程後自動啟動這些任務。線程池線程都是背景線程。每個線程都使用預設的堆棧大小,以預設的優先級運作,并處于多線程單元中。如果某個線程在托管代碼中空閑(如正在等待某個事件),則線程池将插入另一個輔助線程來使所有處理器保持繁忙。如果所有線程池線程都始終保持繁忙,但隊列中包含挂起的工作,則線程池将在一段時間後建立另一個輔助線程但線程的數目永遠不會超過最大值。超過最大值的線程可以排隊,但他們要等到其他線程完成後才啟動。 ---摘自百度百科

二、使用步驟

1.ThreadPoolExecutor參數介紹

參數名稱 參數類型 參數含義
corePoolSize int 核心線程池大小
maximumPoolSize int 最大線程池大小
keepAliveTime long 線程最大空閑時間
unit TimeUnit 時間機關
workQueue BlockingQueue 線程等待隊列
threadFactory ThreadFactory 線程建立工廠
handler RejectedExecutionHandler 拒絕政策

我們看下ThreadPoolExecutor類的execute方法底層源碼進行分析

Java線程池深入淺出ThreadPoolExecutor使用前言一、技術介紹二、使用步驟作者寄語

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 ");


    }

           

此測試方法運作的結果如下:

Java線程池深入淺出ThreadPoolExecutor使用前言一、技術介紹二、使用步驟作者寄語

注意看我用紅框标記的地方,隻采用了1個線程去執行,原理是什麼呢?讓我們看看newSingleThreadExecutor的源碼

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

           

建構了ThreadPoolExecutor線程池,核心線程1個,最大執行線程1個,等待隊列是LinkedBlockingQueue,咱們再點進去看看LinkedBlockingQueue預設構造函數是啥

Java線程池深入淺出ThreadPoolExecutor使用前言一、技術介紹二、使用步驟作者寄語
Java線程池深入淺出ThreadPoolExecutor使用前言一、技術介紹二、使用步驟作者寄語

可以看到這是預設時一個容量為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 ");
    }

           

先來看下執行結果

Java線程池深入淺出ThreadPoolExecutor使用前言一、技術介紹二、使用步驟作者寄語

OK,看下執行結果可知,隻開啟了5個線程,每次批量的執行5個,接下來咱們看看它的源碼

Java線程池深入淺出ThreadPoolExecutor使用前言一、技術介紹二、使用步驟作者寄語

也同樣的構造了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個線程,咋們先看執行結果,

Java線程池深入淺出ThreadPoolExecutor使用前言一、技術介紹二、使用步驟作者寄語

很明顯可以看到跟上面的不同,在執行時間很短的任務時重複的利用線程去執行,原因是什麼呢?咱們先看源碼

Java線程池深入淺出ThreadPoolExecutor使用前言一、技術介紹二、使用步驟作者寄語

建立了一個核心線程數為0,最大執行線程為Interger.MAX_VALUE,并且注意這裡用了SynchronousQueue這個隊列,SynchronousQueue沒有容量,是無緩沖等待隊列,是一個不存儲元素的阻塞隊列,會直接将任務交給消費者,必須等隊列中的添加元素被消費後才能繼續添加新的元素。

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

結論:newCachedThreadPool它是一個可以無限擴大的線程池,目前沒有空閑線程時它會建立一個新的線程,如果有空閑線程會使用空閑線程處理

5.線程池的使用推薦

通過以上的測試案例與源碼分析,相信大家對線程池有了一定的認識,總結如下:

1.newSingleThreadExecutor:隻開啟一個線程運作,處理效率較慢,阻塞隊列大小是沒有大小限制的,如果隊列堆積資料太多會造成資源消耗

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

3.newCachedThreadPool:比較适合處理執行時間較短的業務,但線程若是無限制的建立,可能會導緻記憶體占用過多而産生OOM,并且會造成cpu過度切換消耗太多資源。

是以使用推薦是根據業務場景實作自定義ThreadPoolExecutor,特别是高并發大流量系統,這也是為什麼阿裡内部不推薦使用以上幾種線程池的原因。

作者寄語

是不是感覺很簡單?更多用法請點選下方檢視源碼,關注我帶你揭秘更多進階用法

源碼位址:點此檢視源碼.