java線程池

目錄

 

1、ctl

2、狀態轉換

3、幾個重要的參數及注意事項

<1>corePoolSize

<2>maximumPoolSize

<3>keepAliveTime

<4>workQueue

(1) “直接執行” (常用的隊列是 SynchronousQueue (同步隊列).

(2) 使用無界隊列 (使用 Integer.MAX_VALUE 作爲其默認容量, 例如: 基於鏈表的阻塞隊列 LinkedBlockingQueue).

(3) 使用有界隊列 (例如: 基於數組的阻塞隊列 ArrayBlockingQueue).

<5>RejectedExecutionHandler handler

4、源碼


1、ctl

ctl 是一個 AtomicInteger 對象,包含兩部分信息: 線程池的運行狀態 (runState) 和線程池內有效線程的數量 (workerCount).

高3位來表示線程池的運行狀態, 用低29位來表示線程池內有效線程的數量.

所以線程池理論上最大線程個數是2^29-1

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); // 高3位表示狀態,低29位表示線程池中線程的多少
    private static final int COUNT_BITS = Integer.SIZE - 3; // 32-3 = 29
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1; // 左移29爲減1,即最終得到爲高3位爲0,低29位爲1的數字,作爲掩碼,是二進制運算中常用的方法

    private static final int RUNNING    = -1 << COUNT_BITS; // 高三位111
    private static final int SHUTDOWN   =  0 << COUNT_BITS; // 高三位000
    private static final int STOP       =  1 << COUNT_BITS; // 高三位001
    private static final int TIDYING    =  2 << COUNT_BITS; // 高三位010
    private static final int TERMINATED =  3 << COUNT_BITS; // 高三位011

    // Packing and unpacking ctl
    private static int runStateOf(int c)     { return c & ~CAPACITY; } // 保留高3位,即計算線程池狀態
    private static int workerCountOf(int c)  { return c & CAPACITY; } // 保留低29位, 即計算線程數量
    private static int ctlOf(int rs, int wc) { return rs | wc; } // 求ctl

2、狀態轉換

線程池一共5種狀態:

① RUNNING (運行狀態):

說明:能接受新提交的任務, 並且也能處理阻塞隊列中的任務.

狀態轉換:線程池被一旦被創建,就處於RUNNING狀態,並且線程池中的任務數爲0

② SHUTDOWN (關閉狀態):

說明:不再接受新提交的任務, 但卻可以繼續處理阻塞隊列中已保存的任務.

狀態轉換:在線程池處於 RUNNING 狀態時, 調用 shutdown()方法會使線程池進入到該狀態.

③ STOP :

說明:不能接受新提交的任務, 也不能處理阻塞隊列中已保存的任務, 並且會中斷正在處理中的任務.

狀態轉換:在線程池處於 RUNNING 或 SHUTDOWN 狀態時, 調用 shutdownNow() 方法會使線程池進入到該狀態.

④ TIDYING (清理狀態):

說明:當所有的任務已終止,ctl記錄的”任務數量”爲0,線程池會變爲TIDYING狀態。當線程池變爲TIDYING狀態時,會執行鉤子函數terminated()。(terminated()在ThreadPoolExecutor類中是空的,若用戶想在線程池變爲TIDYING時,進行相應的處理;可以通過重載terminated()函數來實現。 )

狀態轉換:

當線程池在SHUTDOWN狀態下,阻塞隊列爲空並且線程池中執行的任務也爲空時,就會由 SHUTDOWN -> TIDYING。 
當線程池在STOP狀態下,線程池中執行的任務爲空時,就會由STOP -> TIDYING。

⑤ TERMINATED :

說明:線程池徹底終止,就變成TERMINATED狀態。 

狀態轉換:terminated() 方法執行完後就進入該狀態.

isShutDown

當調用shutdown()或shutdownNow()方法後返回爲true。 

 

isTerminated

當調用shutdown()方法後,並且所有提交的任務完成後返回爲true;

當調用shutdownNow()方法後,成功停止後返回爲true;

判斷關閉後所有任務是否都已完成,若關閉後所有任務都已完成,則返回true。

注意除非首先調用shutdown或shutdownNow,否則isTerminated永不爲true。

3、幾個重要的參數及注意事項

<1>corePoolSize

表示的是線程池中一直存活着的線程的最小數量, 這些一直存活着的線程又被稱爲核心線程.

notice1:調用了allowCoreThreadTimeOut()方法並傳遞參數爲true, 設置允許核心線程因超時而停止(terminated), 在那種情況下, 一旦所有的核心線程都先後因超時而停止了, 線程池中的核心線程數量最終會變爲0

notice2:核心線程默認是按需創建並啓動的,即只有當線程池接收到我們提交給他的任務後, 纔會去創建並啓動一定數量的核心線程來執行這些任務. 如果他沒有接收到相關任務, 他就不會主動去創建核心線程. 這種默認的核心線程的創建啓動機制, 有助於降低系統資源的消耗;當然也可以通過調用 prestartCoreThread() 或 prestartAllCoreThreads() 方法來改變這一機制, 使得在新任務還未提交到線程池的時候, 線程池就已經創建並啓動了一個或所有核心線程, 並讓這些核心線程在池子裏等待着新任務的到來.

<2>maximumPoolSize

表示線程池內能夠容納線程數量的最大值. 

<3>keepAliveTime

表示空閒線程處於等待狀態的超時時間(也即, 等待時間的上限值, 超過該時間後該線程會停止工作). 

notice1:

allowCoreThreadTimeOut=false,當非核心線程進入空閒等待時,開始計算各自的等待時間, 並用這裏設定的 keepAliveTime 的數值作爲他們的超時時間, 一旦某個非核心線程的等待時間達到了超時時間, 該線程就會停止工作(terminated),核心線程在這種情況下卻不會受超時機制的制約, 核心線程即使等待的時間超出了這裏設定的 keepAliveTime, 也依然可以繼續處於空閒等待狀態而不會停止工作.

notice2:

如果要執行的任務相對較多,並且每個任務執行的時間比較短,那麼可以爲該參數設置一個相對較大的數值,以提高線程的利用率。

如果執行的任務相對較少, 線程池使用率相對較低, 那麼可以先將該參數設置爲一個較小的數值, 降低系統線程資源的開銷, 後續如果發現線程池的使用率逐漸增高以後, 可以自己手動調用 setKeepAliveTime(long, TimeUnit)方法來重新設定 keepAliveTime 字段的值.

<4>workQueue

workQueue 是一個內部元素爲 Runnable(各種任務, 通常是異步的任務) 的阻塞隊列. 

(1) “直接執行” (常用的隊列是 SynchronousQueue (同步隊列).

這種隊列內部不會存儲元素. 每一次插入操作都會先進入阻塞狀態, 一直等到另一個線程執行了刪除操作, 然後該插入操作纔會執行. 同樣地, 每一次刪除操作也都會先進入阻塞狀態, 一直等到另一個線程執行了插入操作, 然後該刪除操作纔會執行.

當提交一個任務到包含這種 SynchronousQueue 隊列的線程池以後, 線程池會去檢測是否有可用的空閒線程來執行該任務, 如果沒有就直接新建一個線程來執行該任務而不是將該任務先暫存在隊列中.

“直接切換”的意思就是, 處理方式由”將任務暫時存入隊列”直接切換爲”新建一個線程來處理該任務”.

這種策略適合用來處理多個有相互依賴關係的任務, 因爲該策略可以避免這些任務因一個沒有及時處理而導致依賴於該任務的其他任務也不能及時處理而造成的鎖定效果. 因爲這種策略的目的是要讓幾乎每一個新提交的任務都能得到立即處理, 所以這種策略通常要求最大線程數 maximumPoolSizes 是無界的。否則線程池滿了之後就會出現拒絕。

靜態工廠方法 Executors.newCachedThreadPool() 使用了這個隊列。

(2) 使用無界隊列 (使用 Integer.MAX_VALUE 作爲其默認容量, 例如: 基於鏈表的阻塞隊列 LinkedBlockingQueue).

使用無界隊列時線程池中能夠創建的最大線程數就等於核心線程數 corePoolSize, maximumPoolSize 的數值起不到任何作用.

如果向這種線程池中提交一個新任務時發現所有核心線程都處於運行狀態, 那麼該任務將被放入無界隊列中等待處理.

當要處理的多個任務之間沒有任何相互依賴關係時, 就適合使用這種隊列策略來處理這些任務.

靜態工廠方法 Executors.newFixedThreadPool() 使用了這個隊列。

(3) 使用有界隊列 (例如: 基於數組的阻塞隊列 ArrayBlockingQueue).

當要求線程池的最大線程數 maximumPoolSizes 要限定在某個值以內時, 線程池使用有界隊列能夠降低資源的消耗。

通常來說, 設置較大的隊列容量和較小的線程池容量, 能夠降低系統資源的消耗(包括CPU的使用率, 操作系統資源的消耗, 上下文環境切換的開銷等), 但卻會降低線程處理任務的吞吐量. 如果發現提交的任務經常頻繁地發生阻塞的情況, 那麼你就可以考慮增大線程池的容量, 可以通過調用 setMaximumPoolSize() 方法來重新設定線程池的容量.

<5>RejectedExecutionHandler handler

兩個條件會觸發拒絕策略:

① 當線程池處於 SHUTDOWN (關閉) 狀態時 (不論線程池和阻塞隊列是否都已滿) 
② 當線程池中的所有線程都處於運行狀態並且線程池中的阻塞隊列已滿時

幾種具體的拒絕策略:

① AbortPolicy:直接拋異常的處理方式, 拋出 RejectedExecutionException 異常. 

② CallerRunsPolicy:將新提交的任務放在 ThreadPoolExecutor.execute()方法所在的那個線程中執行.

③ DiscardPolicy:直接不執行新提交的任務.

④ DiscardOldestPolicy 這種處理方式分爲兩種情況:

① 當線程池已經關閉 (SHUTDOWN) 時, 就不執行這個任務了, 這也是 DiscardPolicy 的處理方式.

② 當線程池未關閉時, 會將阻塞隊列中處於隊首 (head) 的那個任務從隊列中移除, 然後再將這個新提交的任務加入到該阻塞隊列的隊尾 (tail) 等待執行.

………………

RejectedExecutionHandler 這個參數是個接口, 線程池所提供的這四種方式其實都是該接口的實現類

所以我們可以自定義一個類來實現該接口, 並在重寫該接口的 rejectedExecution() 方法時提供我們自己的處理邏輯。

 

4、源碼

將一個Runnable放到線程池執行有兩種方式,一個是調用ThreadPoolExecutor#submit,一個是調用ThreadPoolExecutor#execute。其實submit是將Runnable封裝成了一個RunnableFuture,然後再調用execute,最終調用的還是execute,所以我們這裏就只從ThreadPoolExecutor#execute開始分析。

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) { // 線程數量小於coresize,那麼就調用addWorker
            if (addWorker(command, true)) 
                return;
            c = ctl.get();
        }

        // 不滿足上述條件,即線程數量 >= coreSize,或者addWorker返回fasle,那麼走下面的邏輯
        if (isRunning(c) && workQueue.offer(command)) { // 可以看到是往blockingqueue中放task
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }

        // 如果不滿足上述條件,即blockingqueue也放不進去,那麼就走下面的邏輯
        else if (!addWorker(command, false))
            reject(command);
    }

如果線程數量小於coresize那麼就執行task,否則就放到queue中,如果queue也放不下就走下面addWorker,如果也失敗了,那麼就調用reject策略。

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