理解ThreadPoolExecutor執行緒池的corePoolSize、maximumPoolSize和poolSize

FrankYou發表於2018-12-18

我們知道,受限於硬體、記憶體和效能,我們不可能無限制的建立任意數量的執行緒,因為每一臺機器允許的最大執行緒是一個有界值。也就是說ThreadPoolExecutor管理的執行緒數量是有界的。執行緒池就是用這些有限個數的執行緒,去執行提交的任務。然而對於多使用者、高併發的應用來說,提交的任務數量非常巨大,一定會比允許的最大執行緒數多很多。為了解決這個問題,必須要引入排隊機制,或者是在記憶體中,或者是在硬碟等容量很大的儲存介質中。J.U.C提供的ThreadPoolExecutor只支援任務在記憶體中排隊,通過BlockingQueue暫存還沒有來得及執行的任務。

任務的管理是一件比較容易的事,複雜的是執行緒的管理,這會涉及執行緒數量、等待/喚醒、同步/鎖、執行緒建立和死亡等問題。ThreadPoolExecutor與執行緒相關的幾個成員變數是:keepAliveTime、allowCoreThreadTimeOut、poolSize、corePoolSize、maximumPoolSize,它們共同負責執行緒的建立和銷燬。

corePoolSize

執行緒池的基本大小,即在沒有任務需要執行的時候執行緒池的大小並且只有在工作佇列滿了的情況下才會建立超出這個數量的執行緒。這裡需要注意的是:在剛剛建立ThreadPoolExecutor的時候,執行緒並不會立即啟動,而是要等到有任務提交時才會啟動,除非呼叫了prestartCoreThread/prestartAllCoreThreads事先啟動核心執行緒。再考慮到keepAliveTime和allowCoreThreadTimeOut超時引數的影響,所以沒有任務需要執行的時候,執行緒池的大小不一定是corePoolSize。

maximumPoolSize

執行緒池中允許的最大執行緒數,執行緒池中的當前執行緒數目不會超過該值。如果佇列中任務已滿,並且當前執行緒個數小於maximumPoolSize,那麼會建立新的執行緒來執行任務。這裡值得一提的是largestPoolSize,該變數記錄了執行緒池在整個生命週期中曾經出現的最大執行緒個數。為什麼說是曾經呢?因為執行緒池建立之後,可以呼叫setMaximumPoolSize()改變執行的最大執行緒的數目。

poolSize

執行緒池中當前執行緒的數量,當該值為0的時候,意味著沒有任何執行緒,執行緒池會終止;同一時刻,poolSize不會超過maximumPoolSize。

現在我們通過ThreadPoolExecutor.execute()方法,看一下這3個屬性的關係,以及執行緒池如何處理新提交的任務。以下原始碼基於JDK1.6.0_37版本。

private boolean addIfUnderCorePoolSize(Runnable firstTask) {
        Thread t = null;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (poolSize < corePoolSize && runState == RUNNING)
                t = addThread(firstTask);
        } finally {
            mainLock.unlock();
        }
        if (t == null)
            return false;
        t.start();
        return true;
    }

    private boolean addIfUnderMaximumPoolSize(Runnable firstTask) {
        Thread t = null;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (poolSize < maximumPoolSize && runState == RUNNING)
                t = addThread(firstTask);
        } finally {
            mainLock.unlock();
        }
        if (t == null)
            return false;
        t.start();
        return true;
    }

 

新提交一個任務時的處理流程很明顯:

1、如果當前執行緒池的執行緒數還沒有達到基本大小(poolSize < corePoolSize),無論是否有空閒的執行緒新增一個執行緒處理新提交的任務;

2、如果當前執行緒池的執行緒數大於或等於基本大小(poolSize >= corePoolSize) 且任務佇列未滿時,就將新提交的任務提交到阻塞佇列排隊,等候處理workQueue.offer(command);

3、如果當前執行緒池的執行緒數大於或等於基本大小(poolSize >= corePoolSize) 且任務佇列滿時

3.1、當前poolSize<maximumPoolSize,那麼就新增執行緒來處理任務;

3.2、當前poolSize=maximumPoolSize,那麼意味著執行緒池的處理能力已經達到了極限,此時需要拒絕新增加的任務。至於如何拒絕處理新增的任務,取決於執行緒池的飽和策略RejectedExecutionHandler。

 

Queuing
Any BlockingQueue may be used to transfer and hold submitted tasks. The use of this queue interacts with pool sizing:

  • If fewer than corePoolSize threads are running, the Executor always prefers adding a new thread rather than queuing.
  • If corePoolSize or more threads are running, the Executor always prefers queuing a request rather than adding a new thread.
  • If a request cannot be queued, a new thread is created unless this would exceed maximumPoolSize, in which case, the task will be rejected.

 

 

    1corePoolSize:核心執行緒數
        * 核心執行緒會一直存活,及時沒有任務需要執行
        * 當執行緒數小於核心執行緒數時,即使有執行緒空閒,執行緒池也會優先建立新執行緒處理
        * 設定allowCoreThreadTimeout=true(預設false)時,核心執行緒會超時關閉

    2queueCapacity:任務佇列容量(阻塞佇列)
        * 當核心執行緒數達到最大時,新任務會放在佇列中排隊等待執行

    3maxPoolSize:最大執行緒數
        * 當執行緒數>=corePoolSize,且任務佇列已滿時。執行緒池會建立新執行緒來處理任務
        * 當執行緒數=maxPoolSize,且任務佇列已滿時,執行緒池會拒絕處理任務而丟擲異常

    4 keepAliveTime:執行緒空閒時間
        * 當執行緒空閒時間達到keepAliveTime時,執行緒會退出,直到執行緒數量=corePoolSize
        * 如果allowCoreThreadTimeout=true,則會直到執行緒數量=0

    5allowCoreThreadTimeout:允許核心執行緒超時
    6rejectedExecutionHandler:任務拒絕處理器
        * 兩種情況會拒絕處理任務:
            - 當執行緒數已經達到maxPoolSize,切佇列已滿,會拒絕新任務
            - 當執行緒池被呼叫shutdown()後,會等待執行緒池裡的任務執行完畢,再shutdown。如果在呼叫shutdown()和執行緒池真正shutdown之間提交任務,會拒絕新任務
        * 執行緒池會呼叫rejectedExecutionHandler來處理這個任務。如果沒有設定預設是AbortPolicy,會丟擲異常
        * ThreadPoolExecutor類有幾個內部實現類來處理這類情況:
            - AbortPolicy 丟棄任務,拋執行時異常
            - CallerRunsPolicy 執行任務
            - DiscardPolicy 忽視,什麼都不會發生
            - DiscardOldestPolicy 從佇列中踢出最先進入佇列(最後一個執行)的任務
        * 實現RejectedExecutionHandler介面,可自定義處理器

接下來我們看下allowCoreThreadTimeOut和keepAliveTime屬性的含義。在壓力很大的情況下,執行緒池中的所有執行緒都在處理新提交的任務或者是在排隊的任務,這個時候執行緒池處在忙碌狀態。如果壓力很小,那麼可能很多執行緒池都處在空閒狀態,這個時候為了節省系統資源,回收這些沒有用的空閒執行緒,就必須提供一些超時機制,這也是執行緒池大小調節策略的一部分。通過corePoolSize和maximumPoolSize,控制如何新增執行緒;通過allowCoreThreadTimeOut和keepAliveTime,控制如何銷燬執行緒

allowCoreThreadTimeOut:

該屬性用來控制是否允許核心執行緒超時退出。預設值為:falseIf false,core threads stay alive even when idle.If true, core threads use keepAliveTime to time out waiting for work。如果執行緒池的大小已經達到了corePoolSize,不管有沒有任務需要執行,執行緒池都會保證這些核心執行緒處於存活狀態。可以知道:該屬性只是用來控制核心執行緒的。

keepAliveTime:

如果一個執行緒處在空閒狀態的時間超過了該屬性值,就會因為超時而退出。舉個例子,如果執行緒池的核心大小corePoolSize=5,而當前大小poolSize =8,那麼超出核心大小的執行緒,會按照keepAliveTime的值判斷是否會超時退出。如果執行緒池的核心大小corePoolSize=5,而當前大小poolSize =5,那麼執行緒池中所有執行緒都是核心執行緒,這個時候執行緒是否會退出,取決於allowCoreThreadTimeOut

    Runnable getTask() {
        for (;;) {
            try {
                int state = runState;
                if (state > SHUTDOWN)
                    return null;
                Runnable r;
                if (state == SHUTDOWN) // Help drain queue
                    r = workQueue.poll();
                else if (poolSize > corePoolSize || allowCoreThreadTimeOut)
                    r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);
                else
                    r = workQueue.take();
                if (r != null)
                    return r;
                if (workerCanExit()) {
                    if (runState >= SHUTDOWN) // Wake up others
                        interruptIdleWorkers();
                    return null;
                }
                // Else retry
            } catch (InterruptedException ie) {
                // On interruption, re-check runState
            }
        }
    }

(poolSize > corePoolSize || allowCoreThreadTimeOut)這個條件,就是用來判斷是否允許當前執行緒退出。workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);就是藉助阻塞佇列,讓空閒執行緒等待keepAliveTime時間之後,恢復執行。這樣空閒執行緒會由於超時而退出。

相關文章