【線程】ThreadPool 線程池底層剖析 (二十)

我的原則:先會用再說,內部慢慢來。
學以致用,根據場景學源碼


一、架構

1.1 UML 圖

在這裏插入圖片描述

1.2 Executors返回的線程池對象的弊端

  1. FixedThreadPool 和 SingleThreadPool : 允許的請求隊列長度爲 Integer.MAX_VALUE ,可能會堆積大量的請求,從而導致 OOM 。
  2. CachedThreadPool 和 ScheduledThreadPool : 允許的創建線程數量爲 Integer.MAX_VALUE ,可能會創建大量的線程,從而導致 OOM 。

=== 點擊查看top目錄 ===

二、 ThreadPoolExecutor 剖析

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

2.1 參數說明

參數 描述
corePoolSize 線程池核心線程數(平時保留的線程數)
maximumPoolSize 線程池最大線程數(線程池最多能起多少Worker)(當workQueue都放不下時,啓動新線程,最大線程數)
keepAliveTime 超出corePoolSize數量的線程的保留時間。
unit keepAliveTime單位
workQueue 阻塞隊列,存放來不及執行的線程
threadFactory 線程工廠
handler 飽和策略
  • 關於 workQueue 阻塞隊列:
  1. ArrayBlockingQueue:構造函數一定要傳大小
  2. LinkedBlockingQueue:構造函數不傳大小會默認爲(Integer.MAX_VALUE ),當大量請求任務時,容易造成 內存耗盡。
  3. SynchronousQueue:同步隊列,一個沒有存儲空間的阻塞隊列 ,將任務同步交付給工作線程。
  4. PriorityBlockingQueue : 優先隊列
  • 關於 handler 飽和策略:
  1. AbortPolicy(默認):直接拋棄
  2. CallerRunsPolicy:用調用者的線程執行任務
  3. DiscardOldestPolicy:拋棄隊列中最久的任務
  4. DiscardPolicy:拋棄當前任務

=== 點擊查看top目錄 ===

2.2 線程池規則

  • 線程池數量無限制:
  1. 如果Worker數量 <= corePoolSize,那麼直接啓動一個核心線程來執行 Task,不會放入隊列中。
  2. 如果 corePoolSize < Worker數量 < maximumPoolSize:
    a.並且任務隊列是LinkedBlockingDeque的時候,超過 corePoolSize 的 Task 會放在任務隊列中排隊。
    b.並且任務隊列是SynchronousQueue的時候,線程池會創建新線程執行任務,這些任務也不會被放在任務隊列中。這些線程屬於非核心線程,在任務完成後,閒置時間達到了超時時間就會被清除。
  3. 如果 maximumPoolSize < Worker數量:
    a.當任務隊列是LinkedBlockingDeque,會將超過核心線程的任務放在任務隊列中排隊。也就是當任務隊列是LinkedBlockingDeque並且沒有大小限制時,線程池的 maximumPoolSize 設置是無效的,他的線程數最多不會超過 corePoolSize。
    b.當任務隊列是SynchronousQueue的時候,會因爲線程池拒絕添加任務而拋出異常。
  • 線程池數量有限制:
  1. 當LinkedBlockingDeque塞滿時,新增的任務會直接創建新線程來執行,當創建的線程數量超過最大線程數量時會拋異常。

synchronousQueue永遠沒有數量限制。因爲他根本不保持這些任務,而是直接交給線程池去執行。當任務數量超過最大線程數時會直接拋異常。

=== 點擊查看top目錄 ===

2.3 線程池處理 UML 圖

在這裏插入圖片描述

2.4 總結 ( 超級重點)

  1. 總共有3個地方可以放 task , core + queue + max
  2. max 池數量意味着同一時間有多少個線程可以併發執行,超過就排隊去。
  3. 到達 core 和 max 的 task 都會立刻執行,但是max內的元素過期會被回收。
  4. 如果 woker 數量少於 corePoolSize,那麼直接就執行 task ,如果是大於 corePoolSize,那麼就暫時塞到 queue 裏面去,等到 corePool 空閒下來,再從 queue拉取 task 來執行,如果queue沒長度限制,那麼可以一直塞到 queue裏面去排隊,如果 queue 滿了,那麼就直接讓 core 去處理,假如此時超過 maximumPoolSize 的話,那麼沒辦法,直接執行拒絕策略。

=== 點擊查看top目錄 ===

三、代碼 Demo

  • 情況:

/** ===== SynchronousQueue ==== **/
//1. 每次+3,塞滿 core ,無法在 queue 排隊, 然後 core 也忙,但是可以丟到 max 裏面去執行。
//2. 每次+3,塞滿 core ,無法在 queue 排隊, 然後 core 也忙,但是可以丟到 max 裏面去執行。
//3. 每次+3,塞滿 core ,無法在 queue 排隊, 然後 core 也忙,但是可以丟到 max 裏面去執行。max 滿了,拋異常。

/** ===== LinkedBlockingDeque ==== **/
// 1. 每次+3,塞滿 core ,在 queue 裏面排隊, 由於 LinkedBlockingDeque 無限制數量,所以可以無限數量的 task 排隊,然後core空閒了就去queue取task
// 2. 每次+3,塞滿 core ,在 queue 裏面排隊,queue滿了,直接丟 max 去執行,後期回收 max 的位置
// 3. 每次+3,塞滿 core ,在 queue 裏面排隊, queue 滿裏,max 也滿了 ,拋出異常

package indi.sword.util.concurrent;

import java.util.concurrent.*;

public class _17_01_TestThreadPool {
    public static void main(String[] args) throws Exception {

        /** ===== SynchronousQueue ==== **/
        //1.  每次+3,塞滿 core ,無法在 queue 排隊, 然後 core 也忙,但是可以丟到 max 裏面去執行。
//        testQueue(6, 8, new SynchronousQueue<>());

        //2. 每次+3,塞滿 core ,無法在 queue 排隊, 然後 core 也忙,但是可以丟到 max 裏面去執行。
//        testQueue(3, 6, new SynchronousQueue<>());

        //3. 每次+3,塞滿 core ,無法在 queue 排隊, 然後 core 也忙,但是可以丟到 max 裏面去執行。max 滿了,拋異常。
//        testQueue(3, 5, new SynchronousQueue<>());// RejectedExecutionException 會導致整個 executor 停止

        /** ===== LinkedBlockingDeque ==== **/
        // 1. 每次+3,塞滿 core ,在 queue 裏面排隊, 由於 LinkedBlockingDeque 無限制數量,所以可以無限數量的 task 排隊,然後core空閒了就去queue取task
//        testQueue(1, 3, new LinkedBlockingDeque<>());

        // 2. 每次+3,塞滿 core ,在 queue 裏面排隊,queue滿了,直接丟 max 去執行,後期回收 max 的位置
//        testQueue(1, 5, new LinkedBlockingDeque<>(1));

        // 3. 每次+3,塞滿 core ,在 queue 裏面排隊, queue 滿裏,max 也滿了 ,拋出異常
        testQueue(1, 3, new LinkedBlockingDeque<>(1));
    }

    /**
     * 隊列任務數永遠是 0
     *
     * @author jeb_lin
     * 5:32 PM 2019/10/24
     */
    public static void testQueue(int corePoolSize,
                                 int maximumPoolSize,
                                 BlockingQueue<Runnable> workQueue) throws Exception {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, 5, TimeUnit.SECONDS, workQueue);
        try {
            System.out.println("輸入: corePoolSize -> " + corePoolSize + ", maximumPoolSize -> " + maximumPoolSize);
            executor.execute(new MyRunnable(1));
            executor.execute(new MyRunnable(2));
            executor.execute(new MyRunnable(3));
            System.out.println("---先開三個---");
            print(executor);
            executor.execute(new MyRunnable(4));
            executor.execute(new MyRunnable(5));
            executor.execute(new MyRunnable(6));
            System.out.println("---再開三個---");
            print(executor);
            Thread.sleep(8000);
            System.out.println("----8秒之後----");
            print(executor);
        } finally {
            executor.shutdown();

        }
    }

    private static void print(ThreadPoolExecutor executor) {
        System.out.println("核心線程數 -> " + executor.getCorePoolSize()
                + ", 線程池數 -> " + executor.getPoolSize()
                + ", 隊列任務數 -> " + executor.getQueue().size()
                + ", queue -> " + executor.getQueue().toString());
    }


    private static class MyRunnable implements Runnable {
        private int index;
        public MyRunnable(int index){
            this.index = index;
        }
        @Override
        public void run() {
            try {
                Thread.sleep(2000);
                System.out.println("thread-" + this.index + " run");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        public String toString(){
            return "thread-" + index;
        }
    }
}

=== 點擊查看top目錄 ===

四、Executors 的 4 個常見方法底層

4.1 Executors 的四個常用方法

方法 描述
newCachedThreadPool 創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。 (線程可複用)
newFixedThreadPool 創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。
newScheduledThreadPool 創建一個定長線程池,支持定時及週期性任務執行。
newSingleThreadExecutor 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。

4.2 newCachedThreadPool 底層

創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。 (線程可複用)

  • java.util.concurrent.Executors#newCachedThreadPool()
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
  1. corePoolSize 竟然爲0 ,也就意味着過期了就全部回收,不剩餘
  2. maximumPoolSize 爲默認的最大值,也就是支持無限制的線程併發,也就意味着可能發生OOM
  3. keepAliveTime 空閒線程的最大等待時間,60s後沒有被複用立馬銷燬
  4. 使用 SynchronousQueue ,說明 queue 並不放東西
  5. corePoolSize = 0 加上 SynchronousQueue,說明 queue + core 都不放東西,那麼也就意味着只有 max 這個地方放東西,時間一到立馬回收。

=== 點擊查看top目錄 ===

4.3 newFixedThreadPool 底層

創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。

  • java.util.concurrent.Executors#newFixedThreadPool(int) 方法
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
  1. corePoolSize = n,規定了 Core 池裏面最多放 n 個線程
  2. maximumPoolSize = n ,規定了池子中最多就是 n 個線程 = corePoolSize,也就是全部都是核心線程,多了排隊。
  3. keepAliveTime = 0,因爲 corePoolSize = maximumPoolSize,也就意味着放滿 core 滿了,不會再放到 max 裏面去,那麼也意味着沒什麼好回收的了。
  4. LinkedBlockingQueue 鏈表內部結構,無定義長度,意味着線程可以不斷進來,不斷去排隊。允許的請求隊列長度爲 Integer.MAX_VALUE ,可能會堆積大量的請求,從而導致 OOM 。
  5. === 關於 LinkedBlockingQueue 與 ArrayBlockingQueue的區別 ===

=== 點擊查看top目錄 ===

4.4 newScheduledThreadPool 底層

創建一個定長線程池,支持定時及週期性任務執行。

  • java.util.concurrent.ScheduledThreadPoolExecutor#ScheduledThreadPoolExecutor(int) 方法
  • java.util.concurrent.Executors#newSingleThreadScheduledExecutor() 方法
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

public ScheduledThreadPoolExecutor(int corePoolSize) {
   super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
          Executors.defaultThreadFactory(), defaultHandler);
 }
  1. corePoolSize 自定義傳入 N ,也就是core池最多n個線程,支持n線程同時併發
  2. maximumPoolSize 爲 Integer.MAX_VALUE ,也就是支持無限制的線程併發,有OOM隱患,這個跟 newCachedThreadPool一樣。
  3. keepAliveTime = 0,意味着,執行完畢立馬回收
  4. 使用 DelayedWorkQueue ,意味着可以執行延遲操作。

4.5 newSingleThreadExecutor 方法

  • java.util.concurrent.Executors#newSingleThreadExecutor() 方法
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
  1. corePoolSize = maximumPoolSize = 1,意味着同一時刻只會有一個線程在執行。
  2. LinkedBlockingQueue 支持無限制的線程併發,有OOM隱患,這個跟 newCachedThreadPool一樣。

=== 點擊查看top目錄 ===

五、源碼剖析

5.1 execute 方法

  • java.util.concurrent.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.
         * 如果當前的線程數小於核心線程池的大小,根據現有的線程作爲第一個Worker運行的線程,
         * 新建一個Worker,addWorker自動的檢查當前線程池的狀態和Worker的數量,
         * 防止線程池在不能添加線程的狀態下添加線程
         *
         * 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.
         *  如果線程入隊成功,然後還是要進行double-check的,因爲線程池在入隊之後狀態是可能會發生變化的
         *
         * 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.
         * 
         * 如果task不能入隊(隊列滿了),這時候嘗試增加一個新線程,如果增加失敗那麼當前的線程池狀態變化了或者線程池已經滿了
         * 然後拒絕task
         */
        int c = ctl.get();
        //當前的Worker的數量小於核心線程池大小時,新建一個Worker。
        if (workerCountOf(c) < corePoolSize) { 
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))//recheck防止線程池狀態的突變,如果突變,那麼將reject線程,防止workQueue中增加新線程
                reject(command);
            else if (workerCountOf(recheck) == 0)//上下兩個操作都有addWorker的操作,但是如果在workQueue.offer的時候Worker變爲0,
                                                //那麼將沒有Worker執行新的task,所以增加一個Worker.
                addWorker(null, false);
        }
        //如果workQueue滿了,那麼這時候可能還沒到線程池的maxnum,所以嘗試增加一個Worker
        else if (!addWorker(command, false))
            reject(command);//如果Worker數量到達上限,那麼就拒絕此線程
    }

… 後續補充上!

=== 點擊查看top目錄 ===

六、番外篇

上一章節:【線程】ThreadPool 線程池 Executors 實戰 (十九)

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