Java源碼解析 - ThreadPoolExecutor 線程池

1 線程池的好處

線程使應用能夠更加充分合理地協調利用CPU、內存、網絡、I/O等系統資源.
線程的創建需要開闢虛擬機棧、本地方法棧、程序計數器等線程私有的內存空間;
在線程銷燬時需要回收這些系統資源.
頻繁地創建和銷燬線程會浪費大量的系統資源,增加併發編程風險.

在服務器負載過大的時候,如何讓新的線程等待或者友好地拒絕服務?

這些都是線程自身無法解決的;
所以需要通過線程池協調多個線程,並實現類似主次線程隔離、定時執行、週期執行等任務.

線程池的作用包括:
●利用線程池管理並複用線程、控制最大併發數等
●實現任務線程隊列緩存策略和拒絕機制
●實現某些與時間相關的功能
如定時執行、週期執行等
●隔離線程環境
比如,交易服務和搜索服務在同一臺服務器上,分別開啓兩個線程池,交易線程的資源消耗明顯要大;
因此,通過配置獨立的線程池,將較慢的交易服務與搜索服務隔離開,避免各服務線程相互影響.

在開發中,合理地使用線程池能夠帶來3個好處

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

在瞭解線程池的基本作用後,我們學習一下線程池是如何創建線程的

2 創建線程池

首先從ThreadPoolExecutor構造方法講起,學習如何自定義ThreadFactoryRejectedExecutionHandler;
並編寫一個最簡單的線程池示例.
然後,通過分析ThreadPoolExecutorexecuteaddWorker兩個核心方法;
學習如何把任務線程加入到線程池中運行.

  • ThreadPoolExecutor 的構造方法如下
    Java源碼解析 - ThreadPoolExecutor 線程池

  • 第1個參數: corePoolSize 表示常駐核心線程數
    如果等於0,則任務執行完之後,沒有任何請求進入時銷燬線程池的線程;
    如果大於0,即使本地任務執行完畢,核心線程也不會被銷燬.
    這個值的設置非常關鍵;
    設置過大會浪費資源;
    設置過小會導致線程頻繁地創建或銷燬.

  • 第2個參數: maximumPoolSize 表示線程池能夠容納同時執行的最大線程數
    從第1處來看,必須>=1.
    如果待執行的線程數大於此值,需要藉助第5個參數的幫助,緩存在隊列中.
    如果maximumPoolSize = corePoolSize,即是固定大小線程池.

  • 第3個參數: keepAliveTime 表示線程池中的線程空閒時間
    當空閒時間達到keepAliveTime時,線程會被銷燬,直到只剩下corePoolSize個線程;
    避免浪費內存和句柄資源.
    在默認情況下,當線程池的線程數大於corePoolSize時,keepAliveTime才起作用.
    但是當ThreadPoolExecutorallowCoreThreadTimeOut = true時,核心線程超時後也會被回收.

  • 第4個參數: TimeUnit表示時間單位
    keepAliveTime的時間單位通常是TimeUnit.SECONDS.

  • 第5個參數: workQueue 表示緩存隊列
    當請求的線程數大於maximumPoolSize時,線程進入BlockingQueue.
    後續示例代碼中使用的LinkedBlockingQueue是單向鏈表,使用鎖來控制入隊和出隊的原子性;
    兩個鎖分別控制元素的添加和獲取,是一個生產消費模型隊列.

  • 第6個參數: threadFactory 表示線程工廠
    它用來生產一組相同任務的線程;
    線程池的命名是通過給這個factory增加組名前綴來實現的.
    在虛擬機棧分析時,就可以知道線程任務是由哪個線程工廠產生的.

  • 第7個參數: handler 表示執行拒絕策略的對象
    當超過第5個參數workQueue的任務緩存區上限的時候,就可以通過該策略處理請求,這是一種簡單的限流保護.
    友好的拒絕策略可以是如下三種:
    (1 ) 保存到數據庫進行削峯填谷;在空閒時再提取出來執行
    (2)轉向某個提示頁面
    (3)打印日誌

2.1.1 corePoolSize(核心線程數量)

線程池中應該保持的主要線程的數量.即使線程處於空閒狀態,除非設置了allowCoreThreadTimeOut這個參數,當提交一個任務到線程池時,若線程數量<corePoolSize,線程池會創建一個新線程放入works(一個HashSet)中執行任務,即使其他空閒的基本線程能夠執行新任務也還是會創建新線程
等到需要執行的任務數大於線程池基本大小時就不再創建,會嘗試放入等待隊列workQueue
如果調用線程池的prestartAllCoreThreads(),線程池會提前創建並啓動所有核心線程
Java源碼解析 - ThreadPoolExecutor 線程池

2.1.2 maximumPoolSize(線程池最大線程數)

線程池允許創建的最大線程數
若隊列滿,並且已創建的線程數小於最大線程數,則線程池會再創建新的線程放入works中執行任務,CashedThreadPool的關鍵,固定線程數的線程池無效
若使用了×××任務隊列,這個參數就沒什麼效果

  • workQueue
    存儲待執行任務的阻塞隊列,這些任務必須是Runnable的對象(如果是Callable對象,會在submit內部轉換爲Runnable對象)
  • runnableTaskQueue(任務隊列):用於保存等待執行的任務的阻塞隊列.可以選擇以下幾個阻塞隊列.

    • LinkedBlockingQueue:一個基於鏈表結構的阻塞隊列,此隊列按FIFO排序元素,吞吐量通常要高於ArrayBlockingQueue.靜態工廠方法Executors.newFixedThreadPool()使用了這個隊列
    • SynchronousQueue:一個不存儲元素的阻塞隊列.每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處於阻塞狀態,吞吐量通常要高於Linked-BlockingQueue,靜態工廠方法Executors.newCachedThreadPool使用了這個隊列

    • ThreadFactory:用於設置創建線程的工廠,可以通過線程工廠給每個創建出來的線程設置更有意義的名字.使用開源框架guava提供ThreadFactoryBuilder可以快速給線程池裏的線程設置有意義的名字,代碼如下
new ThreadFactoryBuilder().setNameFormat("XX-task-%d").build();
  • RejectedExecutionHandler(拒絕策略)
    當隊列和線程池都滿,說明線程池飽和,必須採取一種策略處理提交的新任務
    策略默認AbortPolicy,表無法處理新任務時拋出異常
    在JDK 1.5中Java線程池框架提供了以下4種策略

    • AbortPolicy:丟棄任務,拋出 RejectedExecutionException
    • CallerRunsPolicy:只用調用者所在線程來運行任務,有反饋機制,使任務提交的速度變慢)。
    • DiscardOldestPolicy
      若沒有發生shutdown,嘗試丟棄隊列裏最近的一個任務,並執行當前任務, 丟棄任務緩存隊列中最老的任務,並且嘗試重新提交新的任務
    • DiscardPolicy:不處理,丟棄掉, 拒絕執行,不拋異常
      當然,也可以根據應用場景需要來實現RejectedExecutionHandler接口自定義策略.如記錄日誌或持久化存儲不能處理的任務

      /**
      * Invokes the rejected execution handler for the given command.
      * Package-protected for use by ScheduledThreadPoolExecutor.
      */
      final void reject(Runnable command) {
      // 執行拒絕策略
      handler.rejectedExecution(command, this);
      }

      handler 構造線程池時候就傳的參數,RejectedExecutionHandler的實例
      RejectedExecutionHandlerThreadPoolExecutor 中有四個實現類可供我們直接使用,當然,也可以實現自己的策略,一般也沒必要。

      
      //只要線程池沒有被關閉,由提交任務的線程自己來執行這個任務
      public static class CallerRunsPolicy implements RejectedExecutionHandler {
      
      public CallerRunsPolicy() { }
      
      /**
       * Executes task r in the caller's thread, unless the executor
       * has been shut down, in which case the task is discarded.
       *
       * @param r the runnable task requested to be executed
       * @param e the executor attempting to execute this task
       */
      public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
          if (!e.isShutdown()) {
              r.run();
          }
      }
      }

    // 不管怎樣,直接拋出 RejectedExecutionException 異常
    // 默認的策略,如果我們構造線程池的時候不傳相應的 handler ,則指定使用這個
    public static class AbortPolicy implements RejectedExecutionHandler {

    public AbortPolicy() { }
    
    /**
     * Always throws RejectedExecutionException.
     *
     * @param r the runnable task requested to be executed
     * @param e the executor attempting to execute this task
     * @throws RejectedExecutionException always
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException("Task " + r.toString() +
                                             " rejected from " +
                                             e.toString());
    }

    }

    // 不做任何處理,直接忽略掉這個任務
    public static class DiscardPolicy implements RejectedExecutionHandler {
    /**

    • Creates a {@code DiscardPolicy}.
      */
      public DiscardPolicy() { }

      /**

    • Does nothing, which has the effect of discarding task r.
    • @param r the runnable task requested to be executed
    • @param e the executor attempting to execute this task
      */
      public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
      }
      }

    // 若線程池未被關閉
    // 把隊列隊頭的任務(也就是等待了最長時間的)直接扔掉,然後提交這個任務到等待隊列中
    public static class DiscardOldestPolicy implements RejectedExecutionHandler {

    public DiscardOldestPolicy() { }
    
    /**
     * Obtains and ignores the next task that the executor
     * would otherwise execute, if one is immediately available,
     * and then retries execution of task r, unless the executor
     * is shut down, in which case task r is instead discarded.
     *
     * @param r the runnable task requested to be executed
     * @param e the executor attempting to execute this task
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            e.getQueue().poll();
            e.execute(r);
        }
    }

    }

    
    - keepAliveTime(線程活動保持時間)
    線程沒有任務執行時最多保持多久時間終止
    線程池的工作線程空閒後,保持存活的時間。
    所以,如果任務很多,並且每個任務執行的時間比較短,可以調大時間,提高線程的利用率
  • TimeUnit(線程活動保持時間的單位):指示第三個參數的時間單位;可選的單位有天(DAYS)、小時(HOURS)、分鐘(MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和納秒(NANOSECONDS,千分之一微秒)

從代碼第2處來看,隊列、線程工廠、拒絕處理服務都必須有實例對象;
但在實際編程中,很少有程序員對這三者進行實例化,而通過Executors這個線程池靜態工廠提供默認實現;
那麼Exceutors與ThreadPoolExecutor是什麼關係呢?
線程池相關類圖

Executors工廠類

ExecutorService 的抽象類AbstractExecutorService提供了submitinvokeAll 等方法的實現;
但是核心方法Executor.execute()並沒有在這裏實現.
因爲所有的任務都在該方法執行,不同實現會帶來不同的執行策略.

通過Executors的靜態工廠方法可以創建三個線程池的包裝對象

  • ForkJoinPool、
  • ThreadPoolExecutor
  • ScheduledThreadPoolExecutor

● Executors.newWorkStealingPool
JDK8 引入,創建持有足夠線程的線程池支持給定的並行度;
並通過使用多個隊列減少競爭;
構造方法中把CPU數量設置爲默認的並行度.
返回ForkJoinPool ( JDK7引入)對象,它也是AbstractExecutorService 的子類
Java源碼解析 - ThreadPoolExecutor 線程池

● Executors.newCachedThreadPool
maximumPoolSize 最大可以至Integer.MAX_VALUE,是高度可伸縮的線程池.
若達到該上限,相信沒有服務器能夠繼續工作,直接OOM.
keepAliveTime 默認爲60秒;
工作線程處於空閒狀態,則回收工作線程;
如果任務數增加,再次創建出新線程處理任務.

● Executors.newScheduledThreadPool
線程數最大至Integer.MAX_ VALUE,與上述相同,存在OOM風險.
ScheduledExecutorService接口的實現類,支持定時及週期性任務執行;
相比Timer,ScheduledExecutorService 更安全,功能更強大.
newCachedThreadPool的區別是不回收工作線程.

● Executors.newSingleThreadExecutor
創建一個單線程的線程池,相當於單線程串行執行所有任務,保證按任務的提交順序依次執行.

● Executors.newFixedThreadPool
輸入的參數即是固定線程數;
既是核心線程數也是最大線程數;
不存在空閒線程,所以keepAliveTime等於0.
Java源碼解析 - ThreadPoolExecutor 線程池
其中使用了 LinkedBlockingQueue, 但是沒有設置上限!!!,堆積過多任務!!!

下面介紹LinkedBlockingQueue的構造方法
Java源碼解析 - ThreadPoolExecutor 線程池
使用這樣的×××隊列,如果瞬間請求非常大,會有OOM的風險;
newWorkStealingPool 外,其他四個創建方式都存在資源耗盡的風險.

不推薦使用其中的任何創建線程池的方法,因爲都沒有任何限制,存在安全隱患.

Executors中默認的線程工廠和拒絕策略過於簡單,通常對用戶不夠友好.
線程工廠需要做創建前的準備工作,對線程池創建的線程必須明確標識,就像藥品的生產批號一樣,爲線程本身指定有意義的名稱和相應的序列號.
拒絕策略應該考慮到業務場景,返回相應的提示或者友好地跳轉.
以下爲簡單的ThreadFactory 示例
Java源碼解析 - ThreadPoolExecutor 線程池

上述示例包括線程工廠和任務執行體的定義;
通過newThread方法快速、統一地創建線程任務,強調線程一定要有特定意義的名稱,方便出錯時回溯.

  • 單線程池:newSingleThreadExecutor()方法創建,五個參數分別是ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue())。含義是池中保持一個線程,最多也只有一個線程,也就是說這個線程池是順序執行任務的,多餘的任務就在隊列中排隊。
  • 固定線程池:newFixedThreadPool(nThreads)方法創建
    Java源碼解析 - ThreadPoolExecutor 線程池
    池中保持nThreads個線程,最多也只有nThreads個線程,多餘的任務也在隊列中排隊。
    ThreadPoolExecutor的構造器-1
    ThreadPoolExecutor的構造器-2

線程數固定且線程不超時

  • 緩存線程池:newCachedThreadPool()創建,五個參數分別是ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue())。
    含義是池中不保持固定數量的線程,隨需創建,最多可以創建Integer.MAX_VALUE個線程(說一句,這個數量已經大大超過目前任何操作系統允許的線程數了),空閒的線程最多保持60秒,多餘的任務在SynchronousQueue(所有阻塞、併發隊列在後續文章中具體介紹)中等待。

爲什麼單線程池和固定線程池使用的任務阻塞隊列是LinkedBlockingQueue(),而緩存線程池使用的是SynchronousQueue()呢?
因爲單線程池和固定線程池中,線程數量是有限的,因此提交的任務需要在LinkedBlockingQueue隊列中等待空餘的線程;而緩存線程池中,線程數量幾乎無限(上限爲Integer.MAX_VALUE),因此提交的任務只需要在SynchronousQueue隊列中同步移交給空餘線程即可。

  • 單線程調度線程池:newSingleThreadScheduledExecutor()創建,五個參數分別是 (1, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue())。含義是池中保持1個線程,多餘的任務在DelayedWorkQueue中等待。
  • 固定調度線程池:newScheduledThreadPool(n)創建,五個參數分別是 (n, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue())。含義是池中保持n個線程,多餘的任務在DelayedWorkQueue中等待。

有一項技術可以緩解執行時間較長任務造成的影響,即限定任務等待資源的時間,而不要無限的等待

先看第一個例子,測試單線程池、固定線程池和緩存線程池(注意增加和取消註釋):

public class ThreadPoolExam {
    public static void main(String[] args) {
        //first test for singleThreadPool
        ExecutorService pool = Executors.newSingleThreadExecutor();
        //second test for fixedThreadPool
//        ExecutorService pool = Executors.newFixedThreadPool(2);
        //third test for cachedThreadPool
//        ExecutorService pool = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            pool.execute(new TaskInPool(i));
        }
        pool.shutdown();
    }
}

class TaskInPool implements Runnable {
    private final int id;

    TaskInPool(int id) {
        this.id = id;
    }

    @Override
    public void run() {
        try {
            for (int i = 0; i < 5; i++) {
                System.out.println("TaskInPool-["+id+"] is running phase-"+i);
                TimeUnit.SECONDS.sleep(1);
            }
            System.out.println("TaskInPool-["+id+"] is over");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

如圖爲排查底層公共緩存調用出錯時的截圖
有意義的線程命名
綠色框採用自定義的線程工廠,明顯比藍色框默認的線程工廠創建的線程名稱擁有更多的額外信息:如調用來源、線程的業務含義,有助於快速定位到死鎖、StackOverflowError 等問題.

拒絕策略

下面再簡單地實現一下RejectedExecutionHandler;
實現了接口的rejectedExecution方法,打印出當前線程池狀態
Java源碼解析 - ThreadPoolExecutor 線程池

ThreadPoolExecutor中提供了四個公開的內部靜態類
● AbortPolicy - 默認
丟棄任務並拋出RejectedExecutionException
Java源碼解析 - ThreadPoolExecutor 線程池

● DiscardPolicy - 不推薦
丟棄任務,但不拋異常.
Java源碼解析 - ThreadPoolExecutor 線程池

● DiscardOldestPolicy
拋棄隊列中等待最久的任務,然後把當前任務加入隊列中.
Java源碼解析 - ThreadPoolExecutor 線程池

● CallerRunsPolicy
調用任務的run()方法繞過線程池直接執行.
Java源碼解析 - ThreadPoolExecutor 線程池

根據之前實現的線程工廠和拒絕策略,線程池的相關代碼實現如下
Java源碼解析 - ThreadPoolExecutor 線程池
Java源碼解析 - ThreadPoolExecutor 線程池
當任務被拒絕的時候,拒絕策略會打印出當前線程池的大小已經達到了maximumPoolSize=2,且隊列已滿,完成的任務數提示已經有1個(最後一行).

源碼講解

ThreadPoolExecutor的屬性定義中頻繁地用位運算來表示線程池狀態;
位運算是改變當前值的一種高效手段.

下面從屬性定義開始

Integer 有32位;
最右邊29位表工作線程數;
最左邊3位表示線程池狀態,可表示從0至7的8個不同數值
線程池的狀態用高3位表示,其中包括了符號位.
五種狀態的十進制值按從小到大依次排序爲
RUNNING < SHUTDOWN < STOP < TIDYING <TERMINATED
這樣設計的好處是可以通過比較值的大小來確定線程池的狀態.
例如程序中經常會出現isRunning的判斷:
Java源碼解析 - ThreadPoolExecutor 線程池

Java源碼解析 - ThreadPoolExecutor 線程池

  • 000-1111111111111111111111111;
    類似於子網掩碼,用於與運算;
    得到左邊3位,還是右邊29位
    Java源碼解析 - ThreadPoolExecutor 線程池

用左邊3位,實現5種線程池狀態;
在左3位之後加入中畫線有助於理解;

  • 111 - 0000000000000000000000000000(十進制: -536, 870, 912);
    該狀態表 線程池能接受新任務
    Java源碼解析 - ThreadPoolExecutor 線程池

  • 000 - 0000000000000000000000000(十進制: 0);
    此狀態不再接受新任務,但可繼續執行隊列中的任務
    Java源碼解析 - ThreadPoolExecutor 線程池

  • 001 - 00000000000000000000000000(十進制: 536,870, 912);
    此狀態全面拒絕,並中斷正在處理的任務
    Java源碼解析 - ThreadPoolExecutor 線程池

  • 010 - 00000000000000000000000000.(十進制值: 1, 073, 741, 824);
    該狀態表 所有任務已經被終止
    Java源碼解析 - ThreadPoolExecutor 線程池

  • 101 - 000000000000000000000000000(十進制值: 1, 610,612, 736)
    該狀態表 已清理完現場
    Java源碼解析 - ThreadPoolExecutor 線程池

與運算,比如 001 - 000000000000000000000100011 表 67個工作線程;
掩碼取反: 111 - 00000000000000000000000.,即得到左邊3位001;
表示線程池當前處於STOP狀態
Java源碼解析 - ThreadPoolExecutor 線程池

同理掩碼 000 - 11111111111111111111,得到右邊29位,即工作線程數
Java源碼解析 - ThreadPoolExecutor 線程池

把左3位與右29位或運算,合併成一個值
Java源碼解析 - ThreadPoolExecutor 線程池

我們都知道Executor接口有且只有一個方法execute();
通過參數傳入待執行線程的對象.
下面分析ThreadPoolExecutor關於execute()方法的實現

線程池執行任務的方法如下

   /**
     * Executes the given task sometime in the future.  The task
     * may execute in a new thread or in an existing pooled thread.
     *
     * If the task cannot be submitted for execution, either because this
     * executor has been shutdown or because its capacity has been reached,
     * the task is handled by the current {@code RejectedExecutionHandler}.
     *
     * @param command the task to execute
     * @throws RejectedExecutionException at discretion of
     *         {@code RejectedExecutionHandler}, if the task
     *         cannot be accepted for execution
     * @throws NullPointerException if {@code command} is null
     */
    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
        // 返回包含線程數及線程池狀態的Integer 類型數值
        int c = ctl.get();

        // 若工作線程數 < 核心線程數,則創建線程並執行當前任務
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))

execute方法在不同的階段有三次addWorker的嘗試動作。

                return;
            // 若創建失敗,爲防止外部已經在線程池中加入新任務,在此重新獲取一下
            c = ctl.get();
        }

        // 若 工作線程數 >=核心線程數 或線程創建失敗,則將當前任務放到工作隊列中
        // 只有線程池處於 RUNNING 態,才執行後半句 : 置入隊列
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();

            // 只有線程池處於 RUNNING 態,才執行後半句 : 置入隊列
            if (! isRunning(recheck) && remove(command))
                reject(command);
            // 若之前的線程已被消費完,新建一個線程
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        // 核心線程和隊列都已滿,嘗試創建一個新線程
        }
        else if (!addWorker(command, false))
            // 拋出RejectedExecutionException異常
            // 若 addWorker 返回是 false,即創建失敗,則喚醒拒絕策略.
            reject(command);
    }

發生拒絕的理由有兩個
( 1 )線程池狀態爲非RUNNING狀態
(2)等待隊列已滿。

下面繼續分析addWorker

addWorker 源碼解析

根據當前線程池狀態,檢查是否可以添加新的任務線程,若可以則創建並啓動任務;
若一切正常則返回true;
返回false的可能性如下

  1. 線程池沒有處於RUNNING
  2. 線程工廠創建新的任務線程失敗

    參數

    • firstTask
      外部啓動線程池時需要構造的第一個線程,它是線程的母體
    • core
      新增工作線程時的判斷指標
      • true
        需要判斷當前RUNNING態的線程是否少於corePoolsize
    • false
      需要判斷當前RUNNING態的線程是否少於maximumPoolsize
      Java源碼解析 - ThreadPoolExecutor 線程池
      Java源碼解析 - ThreadPoolExecutor 線程池
      Java源碼解析 - ThreadPoolExecutor 線程池

這段代碼晦澀難懂,部分地方甚至違反代碼規約,但其中蘊含豐富的編碼知識點

  • 第1處,配合循環語句出現的label,類似於goto 作用
    label 定義時,必須把標籤和冒號的組合語句緊緊相鄰定義在循環體之前,否則會編譯出錯.
    目的是 在實現多重循環時能夠快速退出到任何一層;
    出發點似乎非常貼心,但在大型軟件項目中,濫用標籤行跳轉的後果將是災難性的.
    示例代碼中在retry下方有兩個無限循環;
    workerCount加1成功後,直接退出兩層循環.

  • 第2處,這樣的表達式不利於閱讀,應如是
    Java源碼解析 - ThreadPoolExecutor 線程池

  • 第3處,與第1處的標籤呼應,AtomicInteger對象的加1操作是原子性的;
    break retry表 直接跳出與retry 相鄰的這個循環體

  • 第4處,此continue跳轉至標籤處,繼續執行循環.
    如果條件爲false,則說明線程池還處於運行狀態,即繼續在for(;)循環內執行.

  • 第5處,compareAndIncrementWorkerCount方法執行失敗的概率非常低.
    即使失敗,再次執行時成功的概率也是極高的,類似於自旋原理.
    這裏是先加1,創建失敗再減1,這是輕量處理併發創建線程的方式;
    如果先創建線程,成功再加1,當發現超出限制後再銷燬線程,那麼這樣的處理方式明顯比前者代價要大.

  • 第6處,Worker對象是工作線程的核心類實現,部分源碼如下
    Java源碼解析 - ThreadPoolExecutor 線程池
    它實現了Runnable接口,並把本對象作爲參數輸入給run()中的runWorker (this);
    所以內部屬性線程threadstart的時候,即會調用runWorker.

總結

線程池的相關源碼比較精煉,還包括線程池的銷燬、任務提取和消費等,與線程狀態圖一樣,線程池也有自己獨立的狀態轉化流程,本節不再展開。
總結一下,使用線程池要注意如下幾點:
(1)合理設置各類參數,應根據實際業務場景來設置合理的工作線程數。
(2)線程資源必須通過線程池提供,不允許在應用中自行顯式創建線程。
(3)創建線程或線程池時請指定有意義的線程名稱,方便出錯時回溯。

線程池不允許使用Executors,而是通過ThreadPoolExecutor的方式創建,這樣的處理方式能更加明確線程池的運行規則,規避資源耗盡的風險。

進一步查看源碼發現,這些方法最終都調用了ThreadPoolExecutor和ScheduledThreadPoolExecutor的構造函數
而ScheduledThreadPoolExecutor繼承自ThreadPoolExecutor

0.2 ThreadPoolExecutor 自定義線程池

ExecutorService接口的兩個主要的具體實現
它們都是某種線程池,可以控制線程創建,釋放,並通過某種策略嘗試複用線程去執行任務的一個管理框架

,因此最終所有線程池的構造函數都調用了Java5後推出的ThreadPoolExecutor的如下構造函數
Java源碼解析 - ThreadPoolExecutor 線程池

Java默認提供的線程池

Java中的線程池是運用場景最多的併發框架,幾乎所有需要異步或併發執行任務的程序都可以使用線程池
Java源碼解析 - ThreadPoolExecutor 線程池
Java源碼解析 - ThreadPoolExecutor 線程池

我們只需要將待執行的方法放入 run 方法中,將 Runnable 接口的實現類交給線程池的
execute 方法,作爲他的一個參數,比如:

Executor e=Executors.newSingleThreadExecutor();           
e.execute(new Runnable(){ //匿名內部類     public  void run(){  
//需要執行的任務 
} 
}); 

線程池的實現原理

Java源碼解析 - ThreadPoolExecutor 線程池
Java源碼解析 - ThreadPoolExecutor 線程池

ThreadPoolExecutor執行execute()分4種情況

  • 若當前運行的線程少於corePoolSize,則創建新線程來執行任務(執行這一步需要獲取全局鎖)
  • 若運行的線程多於或等於corePoolSize,則將任務加入BlockingQueue
  • 若無法將任務加入BlockingQueue,則創建新的線程來處理任務(執行這一步需要獲取全局鎖)
  • 若創建新線程將使當前運行的線程超出maximumPoolSize,任務將被拒絕,並調用RejectedExecutionHandler.rejectedExecution()

採取上述思路,是爲了在執行execute()時,儘可能避免獲取全局鎖
在ThreadPoolExecutor完成預熱之後(當前運行的線程數大於等於corePoolSize),幾乎所有的execute()方法調用都是執行步驟2,而步驟2不需要獲取全局鎖

#源碼分析

   /**
     * 檢查是否可以根據當前池狀態和給定的邊界(核心或最大)
     * 添加新工作線程。如果是這樣,工作線程數量會相應調整,如果可能的話,一個新的工作線程創建並啓動
     * 將firstTask作爲其運行的第一項任務。
     * 如果池已停止此方法返回false
     * 如果線程工廠在被訪問時未能創建線程,也返回false
     * 如果線程創建失敗,或者是由於線程工廠返回null,或者由於異常(通常是在調用Thread.start()後的OOM)),我們乾淨地回滾。
     *
     * @param core if true use corePoolSize as bound, else
     * maximumPoolSize. (A boolean indicator is used here rather than a
     * value to ensure reads of fresh values after checking other pool
     * state).
     * @return true if successful
     */
    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

    /**
     * Check if queue empty only if necessary.
     * 
     * 如果線程池已關閉,並滿足以下條件之一,那麼不創建新的 worker:
     *      1. 線程池狀態大於 SHUTDOWN,也就是 STOP, TIDYING, 或 TERMINATED
     *      2. firstTask != null
     *      3. workQueue.isEmpty()
     * 簡單分析下:
     *      狀態控制的問題,當線程池處於 SHUTDOWN ,不允許提交任務,但是已有任務繼續執行
     *      當狀態大於 SHUTDOWN ,不允許提交任務,且中斷正在執行任務
     *      多說一句:若線程池處於 SHUTDOWN,但 firstTask 爲 null,且 workQueue 非空,是允許創建 worker 的
     *  
     */
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                // 如果成功,那麼就是所有創建線程前的條件校驗都滿足了,準備創建線程執行任務
                // 這裏失敗的話,說明有其他線程也在嘗試往線程池中創建線程
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                // 由於有併發,重新再讀取一下 ctl
                c = ctl.get();  // Re-read ctl
                // 正常如果是 CAS 失敗的話,進到下一個裏層的for循環就可以了
                // 可如果是因爲其他線程的操作,導致線程池的狀態發生了變更,如有其他線程關閉了這個線程池
                // 那麼需要回到外層的for循環
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

     /* *
        * 到這裏,我們認爲在當前這個時刻,可以開始創建線程來執行任務
        */

        // worker 是否已經啓動
        boolean workerStarted = false;
        // 是否已將這個 worker 添加到 workers 這個 HashSet 中
        boolean workerAdded = false;
        Worker w = null;
        try {
           // 把 firstTask 傳給 worker 的構造方法
            w = new Worker(firstTask);
            // 取 worker 中的線程對象,Worker的構造方法會調用 ThreadFactory 來創建一個新的線程
            final Thread t = w.thread;
            if (t != null) {
               //先加鎖
                final ReentrantLock mainLock = this.mainLock;
                // 這個是整個類的全局鎖,持有這個鎖才能讓下面的操作“順理成章”,
                // 因爲關閉一個線程池需要這個鎖,至少我持有鎖的期間,線程池不會被關閉
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());

                    // 小於 SHUTTDOWN 即 RUNNING
                    // 如果等於 SHUTDOWN,不接受新的任務,但是會繼續執行等待隊列中的任務
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        // worker 裏面的 thread 不能是已啓動的
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        // 加到 workers 這個 HashSet 中
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
               // 若添加成功
                if (workerAdded) {
                    // 啓動線程
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            // 若線程沒有啓動,做一些清理工作,若前面 workCount 加了 1,將其減掉
            if (! workerStarted)
                addWorkerFailed(w);
        }
        // 返回線程是否啓動成功
        return workerStarted;
    }

看下 addWorkFailed
workers 中刪除掉相應的 worker,workCount 減 1<br/>private void addWor

記錄 workers 中的個數的最大值,因爲 workers 是不斷增加減少的,通過這個值可以知道線程池的大小曾經達到的最大值
Java源碼解析 - ThreadPoolExecutor 線程池

worker 中的線程 start 後,其 run 方法會調用 runWorker
Java源碼解析 - ThreadPoolExecutor 線程池
繼續往下看 runWorker

//  worker 線程啓動後調用,while 循環(即自旋!)不斷從等待隊列獲取任務並執行
//  worker 初始化時,可指定 firstTask,那麼第一個任務也就可以不需要從隊列中獲取
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    // 該線程的第一個任務(若有)
    Runnable task = w.firstTask;
    w.firstTask = null;
    // 允許中斷
    w.unlock(); 

    boolean completedAbruptly = true;
    try {
        // 循環調用 getTask 獲取任務
        while (task != null || (task = getTask()) != null) {
            w.lock();          
            // 若線程池狀態大於等於 STOP,那麼意味着該線程也要中斷
              /**
               * 若線程池STOP,請確保線程 已被中斷
               * 如果沒有,請確保線程未被中斷
               * 這需要在第二種情況下進行重新檢查,以便在關中斷時處理shutdownNow競爭
               */
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                // 這是一個鉤子方法,留給需要的子類實現
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    // 到這裏終於可以執行任務了
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    // 這裏不允許拋出 Throwable,所以轉換爲 Error
                    thrown = x; throw new Error(x);
                } finally {
                    // 也是一個鉤子方法,將 task 和異常作爲參數,留給需要的子類實現
                    afterExecute(task, thrown);
                }
            } finally {
                // 置空 task,準備 getTask 下一個任務
                task = null;
                // 累加完成的任務數
                w.completedTasks++;
                // 釋放掉 worker 的獨佔鎖
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        // 到這裏,需要執行線程關閉
        // 1. 說明 getTask 返回 null,也就是說,這個 worker 的使命結束了,執行關閉
        // 2. 任務執行過程中發生了異常
        //    第一種情況,已經在代碼處理了將 workCount 減 1,這個在 getTask 方法分析中說
        //    第二種情況,workCount 沒有進行處理,所以需要在 processWorkerExit 中處理
        processWorkerExit(w, completedAbruptly);
    }
}

看看 getTask()
Java源碼解析 - ThreadPoolExecutor 線程池

// 此方法有三種可能
// 1. 阻塞直到獲取到任務返回。默認 corePoolSize 之內的線程是不會被回收的,它們會一直等待任務
// 2. 超時退出。keepAliveTime 起作用的時候,也就是如果這麼多時間內都沒有任務,那麼應該執行關閉
// 3. 如果發生了以下條件,須返回 null
//     池中有大於 maximumPoolSize 個 workers 存在(通過調用 setMaximumPoolSize 進行設置)
//     線程池處於 SHUTDOWN,而且 workQueue 是空的,前面說了,這種不再接受新的任務
//     線程池處於 STOP,不僅不接受新的線程,連 workQueue 中的線程也不再執行
private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?

   for (;;) {
   // 允許核心線程數內的線程回收,或當前線程數超過了核心線程數,那麼有可能發生超時關閉

            // 這裏 break,是爲了不往下執行後一個 if (compareAndDecrementWorkerCount(c))
            // 兩個 if 一起看:如果當前線程數 wc > maximumPoolSize,或者超時,都返回 null
            // 那這裏的問題來了,wc > maximumPoolSize 的情況,爲什麼要返回 null?
            // 換句話說,返回 null 意味着關閉線程。
            // 那是因爲有可能開發者調用了 setMaximumPoolSize 將線程池的 maximumPoolSize 調小了

            // 如果此 worker 發生了中斷,採取的方案是重試
            // 解釋下爲什麼會發生中斷,這個讀者要去看 setMaximumPoolSize 方法,
            // 如果開發者將 maximumPoolSize 調小了,導致其小於當前的 workers 數量,
            // 那麼意味着超出的部分線程要被關閉。重新進入 for 循環,自然會有部分線程會返回 null
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                // CAS 操作,減少工作線程數
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
            // 如果此 worker 發生了中斷,採取的方案是重試
            // 解釋下爲什麼會發生中斷,這個讀者要去看 setMaximumPoolSize 方法,
            // 如果開發者將 maximumPoolSize 調小了,導致其小於當前的 workers 數量,
            // 那麼意味着超出的部分線程要被關閉。重新進入 for 循環,自然會有部分線程會返回 null
                timedOut = false;
            }
        }
}

到這裏,基本上也說完了整個流程,回到 execute(Runnable command) 方法,看看各個分支,我把代碼貼過來一下:

/**
     * Executes the given task sometime in the future.  The task
     * may execute in a new thread or in an existing pooled thread.
     *
     * If the task cannot be submitted for execution, either because this
     * executor has been shutdown or because its capacity has been reached,
     * the task is handled by the current {@code RejectedExecutionHandler}.
     *
     * @param command the task to execute
     * @throws RejectedExecutionException at discretion of
     *         {@code RejectedExecutionHandler}, if the task
     *         cannot be accepted for execution
     * @throws NullPointerException if {@code command} is null
     */
    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
        //表示 “線程池狀態” 和 “線程數” 的整數
        int c = ctl.get();
        // 如果當前線程數少於核心線程數,直接添加一個 worker 執行任務,
        // 創建一個新的線程,並把當前任務 command 作爲這個線程的第一個任務(firstTask)
        if (workerCountOf(c) < corePoolSize) {
        // 添加任務成功,即結束
        // 執行的結果,會包裝到 FutureTask 
        // 返回 false 代表線程池不允許提交任務
            if (addWorker(command, true))
                return;

            c = ctl.get();
        }

        // 到這說明,要麼當前線程數大於等於核心線程數,要麼剛剛 addWorker 失敗

        // 如果線程池處於 RUNNING ,把這個任務添加到任務隊列 workQueue 中
        if (isRunning(c) && workQueue.offer(command)) {
            /* 若任務進入 workQueue,我們是否需要開啓新的線程
             * 線程數在 [0, corePoolSize) 是無條件開啓新線程的
             * 若線程數已經大於等於 corePoolSize,則將任務添加到隊列中,然後進到這裏
             */
            int recheck = ctl.get();
            // 若線程池不處於 RUNNING ,則移除已經入隊的這個任務,並且執行拒絕策略
            if (! isRunning(recheck) && remove(command))
                reject(command);
            // 若線程池還是 RUNNING ,且線程數爲 0,則開啓新的線程
            // 這塊代碼的真正意圖:擔心任務提交到隊列中了,但是線程都關閉了
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        // 若 workQueue 滿,到該分支
        // 以 maximumPoolSize 爲界創建新 worker,
        // 若失敗,說明當前線程數已經達到 maximumPoolSize,執行拒絕策略
        else if (!addWorker(command, false))
            reject(command);
    }

工作線程:線程池創建線程時,會將線程封裝成工作線程Worker,Worker在執行完任務後,還會循環獲取工作隊列裏的任務來執行.我們可以從Worker類的run()方法裏看到這點

  public void run() {
        try {
            Runnable task = firstTask;
            firstTask = null;
            while (task != null || (task = getTask()) != null) {
                runTask(task);
                task = null;
            }
        } finally {
            workerDone(this);
        }
    }
 boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new Worker(firstTask);

            final Thread t = w.thread;
            if (t != null) {
               //先加鎖
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

線程池中的線程執行任務分兩種情況

  • 在execute()方法中創建一個線程時,會讓這個線程執行當前任務
  • 這個線程執行完上圖中 1 的任務後,會反覆從BlockingQueue獲取任務來執行

線程池的使用

2.2 向線程池提交任務

可以使用兩個方法向線程池提交任務

2.2.1 execute()

用於提交不需要返回值的任務,所以無法判斷任務是否被線程池執行成功.通過以下代碼可知execute()方法輸入的任務是一個Runnable類的實例.

    threadsPool.execute(new Runnable() {
            @Override
            public void run() {
                   // TODO Auto-generated method stub
            }
        });

從運行結果可以看出,單線程池中的線程是順序執行的。固定線程池(參數爲2)中,永遠最多隻有兩個線程併發執行。緩存線程池中,所有線程都併發執行。
第二個例子,測試單線程調度線程池和固定調度線程池。

public class ScheduledThreadPoolExam {
    public static void main(String[] args) {
        //first test for singleThreadScheduledPool
        ScheduledExecutorService scheduledPool = Executors.newSingleThreadScheduledExecutor();
        //second test for scheduledThreadPool
//        ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(2);
        for (int i = 0; i < 5; i++) {
            scheduledPool.schedule(new TaskInScheduledPool(i), 0, TimeUnit.SECONDS);
        }
        scheduledPool.shutdown();
    }
}

class TaskInScheduledPool implements Runnable {
    private final int id;

    TaskInScheduledPool(int id) {
        this.id = id;
    }

    @Override
    public void run() {
        try {
            for (int i = 0; i < 5; i++) {
                System.out.println("TaskInScheduledPool-["+id+"] is running phase-"+i);
                TimeUnit.SECONDS.sleep(1);
            }
            System.out.println("TaskInScheduledPool-["+id+"] is over");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

從運行結果可以看出,單線程調度線程池和單線程池類似,而固定調度線程池和固定線程池類似。
總結:

  • 如果沒有特殊要求,使用緩存線程池總是合適的;
  • 如果只能運行一個線程,就使用單線程池。
  • 如果要運行調度任務,則按需使用調度線程池或單線程調度線程池
  • 如果有其他特殊要求,則可以直接使用ThreadPoolExecutor類的構造函數來創建線程池,並自己給定那五個參數。

2.2.2 submit()

用於提交需要返回值的任務.線程池會返回一個future類型對象,通過此對象可以判斷任務是否執行成功
並可通過get()獲取返回值,get()會阻塞當前線程直到任務完成,而使用get(long timeout,TimeUnit unit)方法則會阻塞當前線程一段時間後立即返回,這時候可能任務沒有執行完.

    Future<Object> future = executor.submit(harReturnValuetask);
        try {
            Object s = future.get();
        } catch (InterruptedException e) {
            // 處理中斷異常
        } catch (ExecutionException e) {
            // 處理無法執行任務異常
        } finally {
            // 關閉線程池
            executor.shutdown();
        }

2.3 關閉線程池

可通過調用線程池的shutdownshutdownNow方法來關閉線程池.
它們的原理是遍歷線程池中的工作線程,然後逐個調用線程的interrupt方法來中斷線程,所以無法響應中斷的任務可能永遠無法終止.
但是它們存在一定的區別

  • shutdownNow首先將線程池的狀態設置成STOP,然後嘗試停止所有的正在執行或暫停任務的線程,並返回等待執行任務的列表
  • shutdown只是將線程池的狀態設置成SHUTDOWN狀態,然後中斷所有沒有正在執行任務的線程.

只要調用了這兩個關閉方法中的任意一個,isShutdown方法就會返回true.
當所有的任務都已關閉後,才表示線程池關閉成功,這時調用isTerminaed方法會返回true.
至於應該調用哪一種方法,應該由提交到線程池的任務的特性決定,通常調用shutdown方法來關閉線程池,若任務不一定要執行完,則可以調用shutdownNow方法.
##2.4 合理配置
要想合理地配置線程池,就必須首先分析任務特性,可從以下幾個角度來分析

  • 任務的性質:CPU密集型任務、IO密集型任務和混合型任務
  • 任務的優先級:高、中和低
  • 任務的執行時間:長、中和短
  • 任務的依賴性:是否依賴其他系統資源,如數據庫連接。

性質不同的任務可以用不同規模的線程池分開處理

  • CPU密集型任務
    應配置儘可能小的線程,配置
    N(CPU)+1或者 N(CPU) * 2
  • I/O密集型任務
    業務讀取較多,線程並不是一直在執行任務,則應配置儘可能多的線程
    N(CPU)/1 - 阻塞係數(0.8~0.9)
  • 混合型的任務,如果可以拆分,將其拆分成一個CPU密集型任務和一個IO密集型任務,只要這兩個任務執行的時間相差不是太大,那麼分解後執行的吞吐量將高於串行執行的吞吐量.如果這兩個任務執行時間相差太大,則沒必要進行分解.

可以通過Runtime.getRuntime().availableProcessors()方法獲得當前設備的CPU個數.

優先級不同的任務可以使用PriorityBlockingQueue處理.它可以讓優先級高
的任務先執行.

注意 如果一直有優先級高的任務提交到隊列裏,那麼優先級低的任務可能永遠不能執行

執行時間不同的任務可以交給不同規模的線程池來處理,或者可以使用優先級隊列,讓執行時間短的任務先執行.

依賴數據庫連接池的任務,因爲線程提交SQL後需要等待數據庫返回結果,等待的時間越長,則CPU空閒時間就越長,那麼線程數應該設置得越大,這樣才能更好地利用CPU.

建議使用有界隊列 有界隊列能增加系統的穩定性和預警能力,可以根據需要設大一點,比如幾千.
假如系統裏後臺任務線程池的隊列和線程池全滿了,不斷拋出拋棄任務的異常,通過排查發現是數據庫出現了問題,導致執行SQL變得非常緩慢,因爲後臺任務線程池裏的任務全是需要向數據庫查詢和插入數據的,所以導致線程池裏的工作線程全部阻塞,任務積壓在線程池裏.
如果我們設置成×××隊列,那麼線程池的隊列就會越來越多,有可能會撐滿內存,導致整個系統不可用,而不只是後臺任務出現問題.

2.5 線程池的監控

如果在系統中大量使用線程池,則有必要對線程池進行監控,方便在出現問題時,可以根據線程池的使用狀況快速定位問題.可通過線程池提供的參數進行監控,在監控線程池的時候可以使用以下屬性:

  • taskCount:線程池需要執行的任務數量
  • completedTaskCount:線程池在運行過程中已完成的任務數量,小於或等於taskCount。
  • largestPoolSize:線程池裏曾經創建過的最大線程數量.通過這個數據可以知道線程池是否曾經滿過.如該數值等於線程池的最大大小,則表示線程池曾經滿過.
  • getPoolSize:線程池的線程數量.如果線程池不銷燬的話,線程池裏的線程不會自動銷燬,所以這個大小隻增不減.
  • getActiveCount:獲取活動的線程數.

通過擴展線程池進行監控.可以通過繼承線程池來自定義線程池,重寫線程池的
beforeExecute、afterExecute和terminated方法,也可以在任務執行前、執行後和線程池關閉前執行一些代碼來進行監控.例如,監控任務的平均執行時間、最大執行時間和最小執行時間等.
這幾個方法在線程池裏是空方法.

protected void beforeExecute(Thread t, Runnable r) { }

2.6 線程池的狀態

1.當線程池創建後,初始爲 running 狀態
2.調用 shutdown 方法後,處 shutdown 狀態,此時不再接受新的任務,等待已有的任務執行完畢
3.調用 shutdownnow 方法後,進入 stop 狀態,不再接受新的任務,並且會嘗試終止正在執行的任務。
4.當處於 shotdown 或 stop 狀態,並且所有工作線程已經銷燬,任務緩存隊列已清空,線程池被設爲 terminated 狀態。

#總結

java 線程池有哪些關鍵屬性?

  • corePoolSize 到 maximumPoolSize 之間的線程會被回收,當然 corePoolSize 的線程也可以通過設置而得到回收(allowCoreThreadTimeOut(true))。
  • workQueue 用於存放任務,添加任務的時候,如果當前線程數超過了 corePoolSize,那麼往該隊列中插入任務,線程池中的線程會負責到隊列中拉取任務。
  • keepAliveTime 用於設置空閒時間,如果線程數超出了 corePoolSize,並且有些線程的空閒時間超過了這個值,會執行關閉這些線程的操作
  • rejectedExecutionHandler 用於處理當線程池不能執行此任務時的情況,默認有拋出 RejectedExecutionException 異常、忽略任務、使用提交任務的線程來執行此任務和將隊列中等待最久的任務刪除,然後提交此任務這四種策略,默認爲拋出異常。
    ##線程池中的線程創建時機?
  • 如果當前線程數少於 corePoolSize,那麼提交任務的時候創建一個新的線程,並由這個線程執行這個任務;
  • 如果當前線程數已經達到 corePoolSize,那麼將提交的任務添加到隊列中,等待線程池中的線程去隊列中取任務;
  • 如果隊列已滿,那麼創建新的線程來執行任務,需要保證池中的線程數不會超過 maximumPoolSize,如果此時線程數超過了 maximumPoolSize,那麼執行拒絕策略。

##任務執行過程中發生異常怎麼處理?
如果某個任務執行出現異常,那麼執行任務的線程會被關閉,而不是繼續接收其他任務。然後會啓動一個新的線程來代替它。

##什麼時候會執行拒絕策略?

  • workers 的數量達到了 corePoolSize,任務入隊成功,以此同時線程池被關閉了,而且關閉線程池並沒有將這個任務出隊,那麼執行拒絕策略。這裏說的是非常邊界的問題,入隊和關閉線程池併發執行,讀者仔細看看 execute 方法是怎麼進到第一個 reject(command) 裏面的。
  • workers 的數量大於等於 corePoolSize,準備入隊,可是隊列滿了,任務入隊失敗,那麼準備開啓新的線程,可是線程數已經達到 maximumPoolSize,那麼執行拒絕策略。

參考

<<碼出高效>>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章