天天看點

線程池的核心線程數該怎麼設定

作者:飛龍在天808

前言

為什麼要用線程池?線程池中的線程可以重複利用,避免了重複建立線程造成的資源開銷。線上程的執行時間比較短,任務比較多的時候非常适合用線程池。

線程池原理及使用

代碼示例

// threadPoolExecutor 最好定義一個全局的,不用每次重建線程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(5));

public testThreadPool() {
    // 方式1 不需要傳回值
    threadPoolExecutor.submit(new Runnable() {
        @Override
        public void run() {
            // 處理相關業務
        }
    });
    
    // 方式2 有傳回值
    Future<String> submit = threadPoolExecutor.submit(new businessWorker("param"));
}


class businessWorker implements Callable<String> {
    public businessWorker(String param) {
    }
    @Override
    public String call() throws Exception {
        
        return null;
    }
}           

線程池執行步驟

線程池的核心線程數該怎麼設定
  1. 如果運作的線程少于核心線程,會嘗試去開啟一個新的核心線程(addWorker),如果有空間的核心線程,也不會去使用,而是去建立一個新的核心線程,直到核心線程建立滿。核心線程建立滿了,線程去隊列中取任務。
  2. 如果運作的線程數等于核心線程數,任務則進入隊列等待核心線程調用。
  3. 如果隊列已滿,則去建立非核心線程。
  4. 如果非核心線程也飽和了,則進入拒絕政策

參數說明

corePoorSize:核心線程數

maximumPoorSize:最大線程數--核心線程數+非核心線程數

keepAliveTime:非核心線程允許空閑時間,超過則非核心線程銷毀

unit:keepAliveTime機關

workQueue:儲存任務的阻塞隊列

threadFactory:建立線程的工廠

hander:線程池飽和的處理方式(拒絕政策)

接下來根據這幾個參數做進一步研究

核心線程數、最大線程數、隊列大小

核心線程數、最大線程數、隊列大小應該設定成多少合适,設定是否合理直接關系到線程池的處理能力。參考網上的說法要根據業務判斷是CPU密集型還是IO密集型

CPU密集型:就會JVM自己内部處理的一些邏輯,額外的IO操作很少

IO密集型:可以了解為經常要和資料庫互動,産生的IO操作比較多

CPU密集型核心線程數:CPU核數(邏輯核) + 1

IO密集型核心線程數:2 * CPU核數(邏輯核)

我們大部分JAVA WEB項目是IO密集型

上面其實就是一個理想狀态的理論值,在實際應用中還會受其它影響,比如一台伺服器不可能隻有這一個應用,這一個應用中也會有其它線程。是以這個核心線程數的設定并不會十分準确,還要線上上使用時來調試這些參數。

首先要通過壓測來設定出一個較為合理的參數。然後簡單做一個線程池的資料監控,根據這些監控資料及線上的運作情況,可以在對線程數做一下适當的調整。線程池中提供了一些資料,可供我們調用

線程池的核心線程數該怎麼設定
public HashMap<String, Object> getPoolInfo() {
    poolInfo.put("activeCount", threadPoolExecutor.getActiveCount());
    poolInfo.put("completedTask", threadPoolExecutor.getCompletedTaskCount());
    poolInfo.put("poolSize", threadPoolExecutor.getPoolSize());
    poolInfo.put("queueSize", threadPoolExecutor.getQueue().size());
    poolInfo.put("taskCount", threadPoolExecutor.getTaskCount());
    return poolInfo;
}

class AbortPolicyCustom implements RejectedExecutionHandler
{
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        log.info("線程處理異常");
        Integer unCompletedTask = Integer.parseInt(String.valueOf(poolInfo.get("unCompletedTask") == null ? 0 : poolInfo.get("unCompletedTask")));
        poolInfo.put("unCompletedTask", unCompletedTask + 1);
    }
}           
{
    "activeCount":0,
    "taskCount":1000,
    "queueSize":0,
    "poolSize":100,
    "completedTask":1000,
    "unCompletedTask": 0
}           

taskCount: 請求過來的總任務數

poolSize:目前線程池的線程數

completeTaskCount:已完成的任務數

activeCount:目前正在運作的線程數

getQueue().size():可以擷取目前隊列中的任務數

unCompletedTask: 未完成任務數(需要通過拒絕政策自定義實作)

經驗總結:

1. 如果業務經常用

  • 要求響應時間越快越好,可以适當将核心線程數和最大線程數調大一點,隊列不用設定太大。這樣既能提高響應,也不會浪費線程資源。
  • 不要求響應時間,隊列可以設定大一點。

2. 如果業務不常使用

核心線程數可以設定小一點,最大線程數可以調成2倍核心線程數,這樣如果核心線程不夠用的話,可以建立非核心線程,待任務執行完後,到達空閑等待時間後銷毀非核心線程,隻保留了少數核心線程數,這樣也不會浪費線程資源。

3. 批量處理大量任務

不宜将核心線程數設定的太大,設定的太大可能會引起線程的上下文切換帶來的問題,也降低了處理速度,可以适當将隊列設定大一點,不要求響應時間,慢慢處理就好了,注意做好拒絕政策處理,避免任務丢失。

拒絕政策

總共有4種拒絕政策

  1. AbortPolicy:抛出異常(預設)
  2. CallerRunsPolicy:送出任務的線程自己執行
  3. DisCardOldestPolicy:把老的任務丢掉
  4. DiscardPolicy:什麼都沒幹,直接丢掉任務

經驗來說,大多數情況使用預設的抛出異常即可,這樣保證即使任務丢失後,也能做好後續處理。

public void testThreadPool() {
    long l = System.currentTimeMillis();

    try {
        threadPoolExecutor.submit(new Runnable() {
            @Override
            public void run() {
                handleMsg(l);
            }
        });
    } catch (Exception e) {
        log.info("任務處理異常:{}", e);
        Integer unCompletedTask = Integer.parseInt(String.valueOf(poolInfo.get("unCompletedTask") == null ? 0 : poolInfo.get("unCompletedTask")));
        poolInfo.put("unCompletedTask", unCompletedTask + 1);
    }
}           

如果第二個政策,讓主線程自己執行,任務可以不丢失,但是如果出現大量任務被拒絕的話,主線程性能會直線下降。