天天看點

線程池的基本原理,看完就懂了

原文位址: http://blog.jboost.cn/2019/07/05/threadpool.html

本文内容是基于研發部門内部的分享整理,記錄下來供學習或回顧。

1. 為什麼要用線程池

  1. 降低資源消耗。通過重複利用已建立的線程降低線程建立、銷毀線程造成的消耗。
  2. 提高響應速度。當任務到達時,任務可以不需要等到線程建立就能立即執行。
  3. 提高線程的可管理性。線程是稀缺資源,如果無限制的建立,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的配置設定、調優和監控

2. ThreadPoolExecutor線程池類參數詳解

參數 說明
corePoolSize 核心線程數量,線程池維護線程的最少數量
maximumPoolSize 線程池維護線程的最大數量
keepAliveTime 線程池除核心線程外的其他線程的最長空閑時間,超過該時間的空閑線程會被銷毀
unit keepAliveTime的機關,TimeUnit中的幾個靜态屬性:NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS
workQueue 線程池所使用的任務緩沖隊列
threadFactory 線程工廠,用于建立線程,一般用預設的即可
handler 線程池對拒絕任務的處理政策

當線程池任務處理不過來的時候(什麼時候認為處理不過來後面描述),可以通過handler指定的政策進行處理,ThreadPoolExecutor提供了四種政策:

  1. ThreadPoolExecutor.AbortPolicy:丢棄任務并抛出RejectedExecutionException異常;也是預設的處理方式。
  2. ThreadPoolExecutor.DiscardPolicy:丢棄任務,但是不抛出異常。
  3. ThreadPoolExecutor.DiscardOldestPolicy:丢棄隊列最前面的任務,然後重新嘗試執行任務(重複此過程)
  4. ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務

可以通過實作RejectedExecutionHandler接口自定義處理方式。

3. 線程池任務執行

3.1. 添加執行任務

  • submit() 該方法傳回一個Future對象,可執行帶傳回值的線程;或者執行想随時可以取消的線程。Future對象的get()方法擷取傳回值。Future對象的cancel(true/false)取消任務,未開始或已完成傳回false,參數表示是否中斷執行中的線程
  • execute() 沒有傳回值。

3.2. 線程池任務送出過程

一個線程送出到線程池的處理流程如下圖

  1. 如果此時線程池中的數量小于corePoolSize,即使線程池中的線程都處于空閑狀态,也要建立新的線程來處理被添加的任務。
  2. 如果此時線程池中的數量等于corePoolSize,但是緩沖隊列workQueue未滿,那麼任務被放入緩沖隊列。
  3. 如果此時線程池中的數量大于等于corePoolSize,緩沖隊列workQueue滿,并且線程池中的數量小于maximumPoolSize,建新的線程來處理被添加的任務。
  4. 如果此時線程池中的數量大于corePoolSize,緩沖隊列workQueue滿,并且線程池中的數量等于maximumPoolSize,那麼通過 handler所指定的政策來處理此任務。
  5. 當線程池中的線程數量大于 corePoolSize時,如果某線程空閑時間超過keepAliveTime,線程将被終止。這樣,線程池可以動态的調整池中的線程數。

總結即:處理任務判斷的優先級為 核心線程corePoolSize、任務隊列workQueue、最大線程maximumPoolSize,如果三者都滿了,使用handler處理被拒絕的任務。

注意:

  1. 當workQueue使用的是***限隊列時,maximumPoolSize參數就變的無意義了,比如new LinkedBlockingQueue(),或者new ArrayBlockingQueue(Integer.MAX_VALUE);
  2. 使用SynchronousQueue隊列時由于該隊列沒有容量的特性,是以不會對任務進行排隊,如果線程池中沒有空閑線程,會立即建立一個新線程來接收這個任務。maximumPoolSize要設定大一點。
  3. 核心線程和最大線程數量相等時keepAliveTime無作用.

3.3. 線程池關閉

  1. shutdown() 不接收新任務,會處理已添加任務
  2. shutdownNow() 不接受新任務,不處理已添加任務,中斷正在處理的任務

4. 常用隊列介紹

  1. ArrayBlockingQueue: 這是一個由數組實作的容量固定的有界阻塞隊列.
  2. SynchronousQueue: 沒有容量,不能緩存資料;每個put必須等待一個take; offer()的時候如果沒有另一個線程在poll()或者take()的話傳回false。
  3. LinkedBlockingQueue: 這是一個由單連結清單實作的預設***的阻塞隊列。LinkedBlockingQueue提供了一個可選有界的構造函數,而在未指明容量時,容量預設為Integer.MAX_VALUE。

  隊列操作:

方法 說明
add 增加一個元索; 如果隊列已滿,則抛出一個異常
remove 移除并傳回隊列頭部的元素; 如果隊列為空,則抛出一個異常
offer 添加一個元素并傳回true; 如果隊列已滿,則傳回false
poll 移除并傳回隊列頭部的元素; 如果隊列為空,則傳回null
put 添加一個元素; 如果隊列滿,則阻塞
take 移除并傳回隊列頭部的元素; 如果隊列為空,則阻塞
element 傳回隊列頭部的元素; 如果隊列為空,則抛出一個異常
peek 傳回隊列頭部的元素; 如果隊列為空,則傳回null

5. Executors線程工廠類

  1. Executors.newCachedThreadPool();

    說明: 建立一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則建立線程.

    内部實作:new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.SECONDS,new SynchronousQueue());

  2. Executors.newFixedThreadPool(int);

    說明: 建立一個定長線程池,可控制線程最大并發數,超出的線程會在隊列中等待。

    内部實作:new ThreadPoolExecutor(nThreads, nThreads,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue());

  3. Executors.newSingleThreadExecutor();

    說明:建立一個單線程化的線程池,它隻會用唯一的工作線程來執行任務,保證所有任務按照順序執行。

    内部實作:new ThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue())

  4. Executors.newScheduledThreadPool(int);

    說明:建立一個定長線程池,支援定時及周期性任務執行。

    内部實作:new ScheduledThreadPoolExecutor(corePoolSize)

【附】阿裡巴巴Java開發手冊中對線程池的使用規範

  1. 【強制】建立線程或線程池時請指定有意義的線程名稱,友善出錯時回溯。

    正例:

    public class TimerTaskThread extends Thread {
        public TimerTaskThread(){
            super.setName("TimerTaskThread"); 
            ...
        }
    }      
  2. 【強制】線程資源必須通過線程池提供,不允許在應用中自行顯式建立線程。

    說明: 使用線程池的好處是減少在建立和銷毀線程上所花的時間以及系統資源的開銷,解決資

    源不足的問題。如果不使用線程池,有可能造成系統建立大量同類線程而導緻消耗完記憶體或者

    “過度切換”的問題。

  3. 【強制】線程池不允許使用 Executors 去建立,而是通過 ThreadPoolExecutor 的方式,這樣

    的處理方式讓寫的同學更加明确線程池的運作規則,規避資源耗盡的風險。

    說明: Executors 傳回的線程池對象的弊端如下:

    1) FixedThreadPool 和 SingleThreadPool:

    允許的請求隊列長度為 Integer.MAX_VALUE,可能會堆積大量的請求,進而導緻 OOM。

    2) CachedThreadPool 和 ScheduledThreadPool:

    允許的建立線程數量為 Integer.MAX_VALUE, 可能會建立大量的線程,進而導緻 OOM。

6. 總結

ThreadPoolExecutor通過幾個核心參數來定義不同類型的線程池,适用于不同的使用場景;其中在任務送出時,會依次判斷corePoolSize, workQueque, 及maximumPoolSize,不同的狀态不同的處理。技術領域水太深,如果不是日常使用,基本一段時間後某些知識點就忘的差不多了,是以階段性地回顧與總結,對夯實自己的技術基礎很有必要。

我的個人部落格位址:http://blog.jboost.cn

我的微信公衆号:jboost-ksxy (一個不隻有技術幹貨的公衆号,歡迎關注,及時擷取更新内容)

————————————————————————————————————