線程池,這一篇或許就夠了

*本篇文章已受權微信公衆號 guolin_blog (郭霖)獨家發佈git

爲何用線程池

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

    >例如:
    >
    >記建立線程消耗時間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 => 該線程池中核心線程數最大值code

    > **核心線程:**
    > 
    > 線程池新建線程的時候,若是當前線程總數小於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<Runnable> 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)

  • 寫做不易!

相關文章
相關標籤/搜索