3.2 執行緒池選擇
執行緒池作為壓測引擎的核心執行器,是構建整個方案的重中之重。第 1 章我們已經講過了執行緒池的常見型別以及適用場景,這裡不多贅述。因為我們選擇的是執行緒模型,為了更好的管理執行緒及任務,我們選擇自定義執行緒池。設計執行緒池引數考慮以下幾點:
- 保障足夠執行緒資源執行測試用例。
- 保障測試任務提交後快速被執行。
- 保障執行緒複用,避免測試過程中頻繁建立和銷燬執行緒。
- 保障不會建立遠超需求的執行緒。
這些條件對應到執行緒池引數上,可以參考 java.util.concurrent.Executors#newFixedThreadPool(int)
方法的內容:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
這裡做幾點修改:
- 將執行緒
keepAliveTime
調整為 60 秒,這樣可以提升執行緒複用機會。 - 我們增加
threadFactory
引數,方便我們列印日誌和排查故障。 -
workQueue
我們限制最大長度,設定為 1,或者使用java.util.concurrent.SynchronousQueue
代替java.util.concurrent.LinkedBlockingQueue
。
演示程式碼如下:
package org.funtester.performance.books.chapter03.section2;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* ThreadFactory 演示類
*/
public class ThreadFactoryDemo {
public static void main(String[] args) {
ThreadFactory threadFactory = new ThreadFactory() {// 建立一個執行緒工廠
AtomicInteger index = new AtomicInteger();// 執行緒安全的執行緒編號
@Override
public Thread newThread(Runnable r) {// 重寫建立執行緒方法
Thread thread = new Thread(r);// 建立執行緒
thread.setName("執行緒-" + index.incrementAndGet());// 設定執行緒名稱
return thread;// 返回建立的執行緒
}
};
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 3, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10), threadFactory);// 建立執行緒池
for (int i = 0; i < 8; i++) {// 向執行緒池提交8個任務
threadPoolExecutor.execute(new Runnable() {// 提交任務
@Override
public void run() {// 任務執行邏輯
System.out.println(System.currentTimeMillis() + " 執行緒池中的執行緒名稱: " + Thread.currentThread().getName());// 輸出執行緒名稱
try {
Thread.sleep(100);// 模擬任務執行時間
} catch (InterruptedException e) {
throw new RuntimeException(e);// 丟擲執行時異常
}
}
});
}
threadPoolExecutor.shutdown();// 關閉執行緒池
}
}
控制檯輸出:
1699978852559 執行緒池中的執行緒名稱: 執行緒-1
1699978852559 執行緒池中的執行緒名稱: 執行緒-2
1699978852559 執行緒池中的執行緒名稱: 執行緒-3
1699978852663 執行緒池中的執行緒名稱: 執行緒-3
1699978852664 執行緒池中的執行緒名稱: 執行緒-2
1699978852664 執行緒池中的執行緒名稱: 執行緒-1
1699978852768 執行緒池中的執行緒名稱: 執行緒-3
1699978852769 執行緒池中的執行緒名稱: 執行緒-2
為了讓每一個執行緒的名字都不一樣,筆者在 ThreadFactory
實現中增加了 AtomicInteger
物件,用於對建立執行緒進行執行緒安全的計數。我們建立了最大執行緒數為 3 的執行緒池,然後利用執行緒工廠設定了每個執行緒的名字,且具有唯一性。為了展示執行緒複用效果,增加了 workQueue
的容量,避擴音交任務時被拒絕。根據列印資訊可以得出實際效果符合預期的結論。
在使用的過程中,如果執行緒在執行任務遭遇中斷情況,會重新建立新的執行緒補充到執行緒池中,相應的執行緒工廠中的計數器就會增加,大於執行緒池最大執行緒數。
以上執行緒池的選擇基於執行緒模型,若是選擇 TPS 模型,一般我們會盡量選擇最大執行緒數遠大於核心執行緒數,workQueue
容量非常小或者使用 java.util.concurrent.SynchronousQueue
,下面是一個簡單的例子。
package org.funtester.performance.books.chapter03.section2;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class TheadPoolForTpsModel {
public static void main(String[] args) {
ThreadFactory threadFactory = new ThreadFactory() {// 建立一個執行緒工廠
AtomicInteger index = new AtomicInteger();// 執行緒安全的執行緒編號
@Override
public Thread newThread(Runnable r) {// 重寫建立執行緒方法
Thread thread = new Thread(r);// 建立執行緒
thread.setName("執行緒-" + index.incrementAndGet());// 設定執行緒名稱
return thread;// 返回建立的執行緒
}
};
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 200, 60L, TimeUnit.SECONDS, new SynchronousQueue<>(), threadFactory);// 建立執行緒池
for (int i = 0; i < 8; i++) {// 向執行緒池提交8個任務
threadPoolExecutor.execute(new Runnable() {// 提交任務
@Override
public void run() {// 任務執行邏輯
System.out.println(System.currentTimeMillis() + " 執行緒池中的執行緒名稱: " + Thread.currentThread().getName());// 輸出執行緒名稱
try {
Thread.sleep(100);// 模擬任務執行時間
} catch (InterruptedException e) {
throw new RuntimeException(e);// 丟擲執行時異常
}
}
});
}
threadPoolExecutor.shutdown();// 關閉執行緒池
}
}
控制檯輸出:
1700012277892 執行緒池中的執行緒名稱: 執行緒-1
1700012277893 執行緒池中的執行緒名稱: 執行緒-4
1700012277892 執行緒池中的執行緒名稱: 執行緒-3
1700012277892 執行緒池中的執行緒名稱: 執行緒-2
1700012277893 執行緒池中的執行緒名稱: 執行緒-6
1700012277893 執行緒池中的執行緒名稱: 執行緒-5
1700012277893 執行緒池中的執行緒名稱: 執行緒-7
1700012277893 執行緒池中的執行緒名稱: 執行緒-8
可以執行緒池建立了 8 個執行緒去執行任務。在實際工作中,最大執行緒數會比例子中設定的 200 還要大,在預估最大執行緒數時要悲觀一些,儘量取較大的執行緒數。雖然遭遇執行緒數達到最大執行緒數時依舊不夠用的時候,可以透過執行緒池的 API 動態調整執行緒池配置,但是非常不優雅,而且操作難度比較高,不建議新手使用。
書的名字:從 Java 開始做效能測試 。
如果本書內容對你有所幫助,希望各位不吝讚賞,讓我可以貼補家用。讚賞兩位數可以提前閱讀未公開章節。我也會嘗試製作本書的影片教程,包括必要的答疑。
FunTester 原創精華
【連載】從 Java 開始效能測試
- 混沌工程、故障測試、Web 前端
- 服務端功能測試
- 效能測試專題
- Java、Groovy、Go
- 白盒、工具、爬蟲、UI 自動化
- 理論、感悟、影片