線程池參數詳解


7個參數的用途

創建線程池一共有7個參數,從源碼可知,corePoolSize和maximumPoolSize都不能小於0,且核心線程數不能大於最大線程數。

corePoolSize

線程池核心線程數量,核心線程不會被回收,即使沒有任務執行,也會保持空閑狀態。

maximumPoolSize

池允許最大的線程數,當線程數量達到corePoolSize,且workQueue隊列塞滿任務了之后,繼續創建線程。

keepAliveTime

超過corePoolSize之后的“臨時線程”的存活時間。

unit

keepAliveTime的單位。

workQueue

當前線程數超過corePoolSize時,新的任務會處在等待狀態,並存在workQueue中,BlockingQueue是一個先進先出的阻塞式隊列實現,底層實現會涉及Java並發的AQS機制

threadFactory

創建線程的工廠類,通常我們會自定一個threadFactory設置線程的名稱,這樣就可以知道線程是由哪個工廠類創建的,可以快速定位。

handler

線程池執行拒絕策略,當線數量達到maximumPoolSize大小,並且workQueue也已經塞滿了任務的情況下,線程池會調用handler拒絕策略來處理請求。

系統默認的拒絕策略有以下幾種:

  1. AbortPolicy:為線程池默認的拒絕策略,該策略直接拋異常處理。

  2. DiscardPolicy:直接拋棄不處理。

  3. DiscardOldestPolicy:丟棄隊列中最老的任務。

  4. CallerRunsPolicy:將任務分配給當前執行execute方法線程來處理。

我們還可以自定義拒絕策略,只需要實現RejectedExecutionHandler接口即可,友好的拒絕策略實現有如下:

  1. 將數據保存到數據,待系統空閑時再進行處理

  2. 將數據用日志進行記錄,后由人工處理

ThreadPoolExecutor創建線程方式

通過下面的demo來了解ThreadPoolExecutor創建線程的過程。

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
 
/**
 * 測試ThreadPoolExecutor對線程的執行順序
 **/
public class ThreadPoolSerialTest {
    public static void main(String[] args) {
        //核心線程數
        int corePoolSize = 3;
        //最大線程數
        int maximumPoolSize = 6;
        //超過 corePoolSize 線程數量的線程最大空閑時間
        long keepAliveTime = 2;
        //以秒為時間單位
        TimeUnit unit = TimeUnit.SECONDS;
        //創建工作隊列,用於存放提交的等待執行任務
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<Runnable>(2);
        ThreadPoolExecutor threadPoolExecutor = null;
        try {
            //創建線程池
            threadPoolExecutor = new ThreadPoolExecutor(corePoolSize,
                    maximumPoolSize,
                    keepAliveTime,
                    unit,
                    workQueue,
                    new ThreadPoolExecutor.AbortPolicy());
 
            //循環提交任務
            for (int i = 0; i < 8; i++) {
                //提交任務的索引
                final int index = (i + 1);
                threadPoolExecutor.submit(() -> {
                    //線程打印輸出
                    System.out.println("大家好,我是線程:" + index);
                    try {
                        //模擬線程執行時間,10s
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
                //每個任務提交后休眠500ms再提交下一個任務,用於保證提交順序
                Thread.sleep(500);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            threadPoolExecutor.shutdown();
        }
    }
}

執行結果:

 

這里描述一下執行的流程:

  • 首先通過 ThreadPoolExecutor 構造函數創建線程池;

  • 執行 for 循環,提交 8 個任務(恰好等於maximumPoolSize[最大線程數] + capacity[隊列大小]);

  • 通過 threadPoolExecutor.submit 提交 Runnable 接口實現的執行任務;

  • 提交第1個任務時,由於當前線程池中正在執行的任務為 0 ,小於 3(corePoolSize 指定),所以會創建一個線程用來執行提交的任務1;

  • 提交第 2, 3 個任務的時候,由於當前線程池中正在執行的任務數量小於等於 3 (corePoolSize 指定),所以會為每一個提交的任務創建一個線程來執行任務;

  • 當提交第4個任務的時候,由於當前正在執行的任務數量為 3 (因為每個線程任務執行時間為10s,所以提交第4個任務的時候,前面3個線程都還在執行中),此時會將第4個任務存放到 workQueue 隊列中等待執行;

  • 由於 workQueue 隊列的大小為 2 ,所以該隊列中也就只能保存 2 個等待執行的任務,所以第5個任務也會保存到任務隊列中;

  • 當提交第6個任務的時候,因為當前線程池正在執行的任務數量為3,workQueue 隊列中存儲的任務數量也滿了,這時會判斷當前線程池中正在執行的任務的數量是否小於6(maximumPoolSize指定);

  • 如果小於 6 ,那么就會新創建一個線程來執行提交的任務 6;

  • 執行第7,8個任務的時候,也要判斷當前線程池中正在執行的任務數是否小於6(maximumPoolSize指定),如果小於6,那么也會立即新建線程來執行這些提交的任務;

  • 此時,6個任務都已經提交完畢,那 workQueue 隊列中的等待 任務4 和 任務5 什么時候執行呢?

  • 當任務1執行完畢后(10s后),執行任務1的線程並沒有被銷毀掉,而是獲取 workQueue 中的任務4來執行;

  • 當任務2執行完畢后,執行任務2的線程也沒有被銷毀,而是獲取 workQueue 中的任務5來執行;

通過上面流程的分析,也就知道了之前案例的輸出結果的原因。其實,線程池中會線程執行完畢后,並不會被立刻銷毀,線程池中會保留 corePoolSize 數量的線程,當 workQueue 隊列中存在任務或者有新提交任務時,那么會通過線程池中已有的線程來執行任務,避免了頻繁的線程創建與銷毀,而大於 corePoolSize 小於等於 maximumPoolSize 創建的線程,則會在空閑指定時間(keepAliveTime)后進行回收。

ThreadPoolExecutor拒絕策略

在上面的測試中,我設置的執行線程總數恰好等於maximumPoolSize[最大線程數] + capacity[隊列大小],因此沒有出現需要執行拒絕策略的情況,因此在這里,我再增加一個線程,提交9個任務,來演示不同的拒絕策略。

1、AbortPolicy

09f46d29aaee89592e9113a84d3c1c9e.png

2、CallerRunsPolicy

*bf4b8754c4616c781da520a17d10071d.png*

3、DiscardPolicy

47c534643838b8757202ac4ffb6813e3.png

4、DiscardOldestPolicy

dea00214a0b24a3fc71e1280b71e1ebe.png 

Demo2

public class ThreadPool {

    public static void main(String[] args) {
        // 創建線程池 , 參數含義 :(核心線程數,最大線程數,加開線程的存活時間,時間單位,任務隊列長度)
        ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 8,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>(2));

        //設置a的值范圍在:a = (corePoolSize-1) ~ (max+queue+1) ,分析:任務數 與 活躍線程數,核心線程數,隊列長度,最大線程數的關系。
        int a = 7;

        for (int i = 1; i <= a; i++) {
            int j = i;
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    //獲取線程名稱
                    Thread thread = Thread.currentThread();
                    String name = thread.getName();
                    //輸出
                    int activeCount = pool.getActiveCount();
                    System.out.println("任務:"+j+"-----,線程名稱:"+name+"-----活躍線程數:"+activeCount);
                }
            });
        }
        //關閉線程池
        pool.shutdown();
    }
}

輸出結果,觀察關系:

任務數 a = 4 , 活躍線程數4 , 任務數 < 核心線程數。

任務數 a = 5 , 活躍線程數5 , 任務數 = 核心線程數。

任務數 a = 6 , 活躍線程數5 , 任務數 < 核心線程數5 + 隊列長度2 。

任務數 a = 7 , 活躍線程數5 , 任務數 = 核心線程數5 + 隊列長度2 。

任務數 a = 8 , 活躍線程數6 , 任務數 < 最大線程數8 + 隊列長度2 . 活躍線程數是在核心線程數5的基礎上.加1個活躍線程。

任務數 a = 9 , 活躍線程數7 , 任務數 < 最大線程數8 + 隊列長度2.  活躍線程數是在核心線程數5的基礎上.加2個活躍線程。

任務數 a = 10 , 活躍線程數8 , 任務數 = 最大線程數8 + 隊列長度2.  活躍線程數是在核心線程數5的基礎上.加3個活躍線程。

任務數 a = 11 , 活躍線程數8 , 任務數 > 最大線程數8 + 隊列長度2 。拋出異常RejectedExecutionException

總結:

1、隨着任務數量的增加,會增加活躍的線程數。

2、當活躍的線程數 =   核心線程數,此時不再增加活躍線程數,而是往任務隊列里堆積。

3、當任務隊列堆滿了,隨着任務數量的增加,會在核心線程數的基礎上加開線程。

4、直到活躍線程數 = 最大線程數,就不能增加線程了。

5、如果此時任務還在增加,則: 任務數11 > 最大線程數8 + 隊列長度2 ,默認的拒絕策略會拋出異常RejectedExecutionException,拒絕任務。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM