1. 程式人生 > >執行緒池,這一篇或許就夠了

執行緒池,這一篇或許就夠了

*本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家釋出

為什麼用執行緒池

  1. 建立/銷燬執行緒伴隨著系統開銷,過於頻繁的建立/銷燬執行緒,會很大程度上影響處理效率

    例如:

    記建立執行緒消耗時間T1,執行任務消耗時間T2,銷燬執行緒消耗時間T3

    如果T1+T3>T2,那麼是不是說開啟一個執行緒來執行這個任務太不划算了!

    正好,執行緒池快取執行緒,可用已有的閒置執行緒來執行新任務,避免了T1+T3帶來的系統開銷

  2. 執行緒併發數量過多,搶佔系統資源從而導致阻塞

    我們知道執行緒能共享系統資源,如果同時執行的執行緒過多,就有可能導致系統資源不足而產生阻塞的情況

    運用執行緒池能有效的控制執行緒最大併發數,避免以上的問題

  3. 對執行緒進行一些簡單的管理

    比如:延時執行、定時迴圈執行的策略等

    運用執行緒池都能進行很好的實現

執行緒池ThreadPoolExecutor

既然Android中執行緒池來自於Java,那麼研究Android執行緒池其實也可以說是研究Java中的執行緒池

在Java中,執行緒池的概念是Executor這個介面,具體實現為ThreadPoolExecutor類,學習Java中的執行緒池,就可以直接學習他了

對執行緒池的配置,就是對ThreadPoolExecutor建構函式的引數的配置,既然這些引數這麼重要,就來看看建構函式的各個引數吧

ThreadPoolExecutor提供了四個建構函式


//五個引數的建構函式
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue)

//六個引數的建構函式-1
public
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) //六個引數的建構函式-2 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) //七個引數的建構函式 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)

我知道你看到這些建構函式和我一樣也是嚇呆了,但其實一共就7種類型,理解起來簡直和理解一週有7天一樣簡單,而且一週有兩天是週末,其實也就只有5天需要了解!相信我,畢竟扯皮,我比較擅長

  • int corePoolSize => 該執行緒池中核心執行緒數最大值

    核心執行緒:

    執行緒池新建執行緒的時候,如果當前執行緒總數小於corePoolSize,則新建的是核心執行緒,如果超過corePoolSize,則新建的是非核心執行緒

    核心執行緒預設情況下會一直存活線上程池中,即使這個核心執行緒啥也不幹(閒置狀態)。

    如果指定ThreadPoolExecutor的allowCoreThreadTimeOut這個屬性為true,那麼核心執行緒如果不幹活(閒置狀態)的話,超過一定時間(時長下面引數決定),就會被銷燬掉

    很好理解吧,正常情況下你不幹活我也養你,因為我總有用到你的時候,但有時候特殊情況(比如我自己都養不起了),那你不幹活我就要把你幹掉了

  • int maximumPoolSize

    該執行緒池中執行緒總數最大值

    執行緒總數 = 核心執行緒數 + 非核心執行緒數。核心執行緒在上面解釋過了,這裡說下非核心執行緒:

    不是核心執行緒的執行緒(別激動,把刀放下…),其實在上面解釋過了

  • long keepAliveTime

    該執行緒池中非核心執行緒閒置超時時長

    一個非核心執行緒,如果不幹活(閒置狀態)的時長超過這個引數所設定的時長,就會被銷燬掉

    如果設定allowCoreThreadTimeOut = true,則會作用於核心執行緒

  • TimeUnit unit

    keepAliveTime的單位,TimeUnit是一個列舉型別,其包括:

    1. NANOSECONDS : 1微毫秒 = 1微秒 / 1000
    2. MICROSECONDS : 1微秒 = 1毫秒 / 1000
    3. MILLISECONDS : 1毫秒 = 1秒 /1000
    4. SECONDS : 秒
    5. MINUTES : 分
    6. HOURS : 小時
    7. DAYS : 天
  • BlockingQueue workQueue

    該執行緒池中的任務佇列:維護著等待執行的Runnable物件

    當所有的核心執行緒都在幹活時,新新增的任務會被新增到這個佇列中等待處理,如果佇列滿了,則新建非核心執行緒執行任務

    常用的workQueue型別:

    1. SynchronousQueue:這個佇列接收到任務的時候,會直接提交給執行緒處理,而不保留它,如果所有執行緒都在工作怎麼辦?那就新建一個執行緒來處理這個任務!所以為了保證不出現<執行緒數達到了maximumPoolSize而不能新建執行緒>的錯誤,使用這個型別佇列的時候,maximumPoolSize一般指定成Integer.MAX_VALUE,即無限大

    2. LinkedBlockingQueue:這個佇列接收到任務的時候,如果當前執行緒數小於核心執行緒數,則新建執行緒(核心執行緒)處理任務;如果當前執行緒數等於核心執行緒數,則進入佇列等待。由於這個佇列沒有最大值限制,即所有超過核心執行緒數的任務都將被新增到佇列中,這也就導致了maximumPoolSize的設定失效,因為匯流排程數永遠不會超過corePoolSize

    3. ArrayBlockingQueue:可以限定佇列的長度,接收到任務的時候,如果沒有達到corePoolSize的值,則新建執行緒(核心執行緒)執行任務,如果達到了,則入隊等候,如果佇列已滿,則新建執行緒(非核心執行緒)執行任務,又如果匯流排程數到了maximumPoolSize,並且佇列也滿了,則發生錯誤

    4. DelayQueue:佇列內元素必須實現Delayed介面,這就意味著你傳進去的任務必須先實現Delayed介面。這個佇列接收到任務時,首先先入隊,只有達到了指定的延時時間,才會執行任務

  • ThreadFactory threadFactory

    建立執行緒的方式,這是一個介面,你new他的時候需要實現他的Thread newThread(Runnable r)方法,一般用不上,這是星期六,休息

    但我還是說一句吧(把槍放下…)

    小夥伴應該知道AsyncTask是對執行緒池的封裝吧?那就直接放一個AsyncTask新建執行緒池的threadFactory引數原始碼吧:

    new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);
    
        public Thread new Thread(Runnable r) {
            return new Thread(r,"AsyncTask #" + mCount.getAndIncrement());
        }
    }

    這麼簡單?就給執行緒起了個名?!對啊,所以說這是星期六啊,別管他了,雖然我已經強迫你們看完了…

  • RejectedExecutionHandler handler

    這玩意兒就是丟擲異常專用的,比如上面提到的兩個錯誤發生了,就會由這個handler丟擲異常,你不指定他也有個預設的

    拋異常能丟擲什麼花樣來?所以這個星期天不管了,一邊去,根本用不上

新建一個執行緒池的時候,一般只用5個引數的建構函式。

向ThreadPoolExecutor新增任務

那說了這麼多,你可能有疑惑,我知道new一個ThreadPoolExecutor,大概知道各個引數是幹嘛的,可是我new完了,怎麼向執行緒池提交一個要執行的任務啊?

通過ThreadPoolExecutor.execute(Runnable command)方法即可向執行緒池內新增一個任務

ThreadPoolExecutor的策略

上面介紹引數的時候其實已經說到了ThreadPoolExecutor執行的策略,這裡給總結一下,當一個任務被新增進執行緒池時:

  1. 執行緒數量未達到corePoolSize,則新建一個執行緒(核心執行緒)執行任務
  2. 執行緒數量達到了corePools,則將任務移入佇列等待
  3. 佇列已滿,新建執行緒(非核心執行緒)執行任務
  4. 佇列已滿,匯流排程數又達到了maximumPoolSize,就會由上面那位星期天(RejectedExecutionHandler)丟擲異常

常見四種執行緒池

如果你不想自己寫一個執行緒池,那麼你可以從下面看看有沒有符合你要求的(一般都夠用了),如果有,那麼很好你直接用就行了,如果沒有,那你就老老實實自己去寫一個吧

Java通過Executors提供了四種執行緒池,這四種執行緒池都是直接或間接配置ThreadPoolExecutor的引數實現的,下面我都會貼出這四種執行緒池建構函式的原始碼,各位大佬們一看便知!

來,走起:

CachedThreadPool()

可快取執行緒池:

  1. 執行緒數無限制
  2. 有空閒執行緒則複用空閒執行緒,若無空閒執行緒則新建執行緒
  3. 一定程式減少頻繁建立/銷燬執行緒,減少系統開銷

建立方法:

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

原始碼:

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

通過我上面行雲流水談笑風生天馬行空滔滔不絕的對各種引數的說明,這個原始碼你肯定一眼就看懂了,想都不用想(下面三種一樣啦)

FixedThreadPool()

定長執行緒池:

  1. 可控制執行緒最大併發數(同時執行的執行緒數)
  2. 超出的執行緒會在佇列中等待

建立方法:

//nThreads => 最大執行緒數即maximumPoolSize
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(int nThreads);

//threadFactory => 建立執行緒的方法,這就是我叫你別理他的那個星期六!你還看!
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(int nThreads, ThreadFactory threadFactory);

原始碼:

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

2個引數的構造方法原始碼,不用我貼你也知道他把星期六放在了哪個位置!所以我就不貼了,省下篇幅給我扯皮

ScheduledThreadPool()

定長執行緒池:

  1. 支援定時及週期性任務執行。

建立方法:

//nThreads => 最大執行緒數即maximumPoolSize
ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(int corePoolSize);

原始碼:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

//ScheduledThreadPoolExecutor():
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE,
          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
          new DelayedWorkQueue());
}

SingleThreadExecutor()

單執行緒化的執行緒池:

  1. 有且僅有一個工作執行緒執行任務
  2. 所有任務按照指定順序執行,即遵循佇列的入隊出隊規則

建立方法:

ExecutorService singleThreadPool = Executors.newSingleThreadPool();

原始碼:

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

還有一個Executors.newSingleThreadScheduledExecutor()結合了3和4,就不介紹了,基本不用。

結語

牆裂建議各位看完本文一定要實際動手去敲一遍都驗證一遍,這樣才能很好的掌握知識

動手做,永遠是學習的最好的方式!

end

  • 更多內容歡迎訪問我的主頁我的部落格
  • 如果我的文章確實有幫助到你,請不要忘了點一下文末的”♡”讓他變成”❤”
  • 作為小菜鳥難免很多地方理解不到位,文中若有錯誤請直(bu)接(yao)指(ma)出(wo)
  • 寫作不易!