IO密集型任務(wù)設(shè)置線程池線程數(shù)實現(xiàn)方式
任務(wù)類型
CPU密集
CPU密集型的話,一般配置CPU處理器個數(shù)+/-1個線程,所謂CPU密集型就是指系統(tǒng)大部分時間是在做程序正常的計算任務(wù),例如數(shù)字運(yùn)算、賦值、分配內(nèi)存、內(nèi)存拷貝、循環(huán)、查找、排序等,這些處理都需要CPU來完成。
IO密集
IO密集型的話,是指系統(tǒng)大部分時間在跟I/O交互,而這個時間線程不會占用CPU來處理,即在這個時間范圍內(nèi),可以由其他線程來使用CPU,因而可以多配置一些線程。(線程處于io等待或則阻塞狀態(tài)時,不會占用CPU資源)
混合型
混合型的話,是指兩者都占有一定的時間。
實際上工作中的大部分場景中,線程池的能力往往會超出想象。
測試準(zhǔn)備
下面的計算方式很粗略,而且有漏洞,但是也可以作為一個參考
處理器信息

四核8線程 (超線程)
任務(wù)示例
我們首先確認(rèn)一下單個任務(wù)的io時間占比,下面是測試代碼
class ThreadPoolTest {
public static int PARK_TIME = 0;
public static void main(String[] args) throws ExecutionException, InterruptedException {
runTask(1);
}
public static void runTask(int threadNum) throws ExecutionException, InterruptedException {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
threadNum, threadNum, 1, TimeUnit.SECONDS, new LinkedBlockingDeque<>(100)
);
long start = System.currentTimeMillis();
List<Future<?>> taskList = new ArrayList<>();
for (int i = 0; i < 1; i++) {
taskList.add(threadPoolExecutor.submit(() -> {
doJob();
}));
}
for (Future<?> future : taskList) {
future.get();
}
long time = System.currentTimeMillis() - start;
System.out.println(threadNum + "個線程,耗時:" + time + "停頓占比" + (PARK_TIME * 100.0 / time));
threadPoolExecutor.shutdown();
}
public static Long doJob() {
long result = 0L;
PARK_TIME = 0;
for (int i = 0; i < Integer.MAX_VALUE; i++) {
if (i % 10_000_000 == 0) {
try {
++PARK_TIME;
// 模擬IO
LockSupport.parkNanos(100_000_000);
} catch (Exception ignore) {
}
}
result += i;
}
return result;
}
}執(zhí)行結(jié)果
直接運(yùn)行 輸出如下:
1個線程,耗時:27862停頓占比0.7716603258918958
也就是說大概77%的時間線程在睡覺。
分析
按我電腦的配置可以認(rèn)為核心數(shù)coreNum為8, 假設(shè)任務(wù)數(shù)夠多的情況下。
不考慮上下文切換等的耗時,單個任務(wù)io耗時占比為x,在線程數(shù)最少的情況下想讓cpu利用率達(dá)到最高,可以得出一個等式 1 / (1 - x) * coreNum = 100% * coreNum(我們假設(shè)線程在活躍狀態(tài)時能完全占用單個核心)。
代入上面得到的值 x = 0.77, coreNum = 8 可以比較容易的算出來如果想讓cpu利用率達(dá)到最高, 1 / (1 - 0.77) * 8 約等于34。即線程池的線程數(shù)設(shè)置為35比較合理。
在我的電腦上合適的線程數(shù)和任務(wù)io耗時占比x的關(guān)系大致可以認(rèn)為 1 / (1 - x) * 8,即理論上的圖如下,這是一個非常粗糙的等式,實際上隨著線程數(shù)增多,上下文切換帶來的開銷越來越大,和下面這張圖的出入還是蠻大的。

不同線程數(shù)下的程序總執(zhí)行耗時
下面簡單修改下程序來驗證一下任務(wù)量固定,不同線程數(shù)下的程序執(zhí)行耗時。
代碼
class ThreadPoolTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
List<Integer> threadNumList = Arrays.asList(4, 8, 16, 25, 34, 50);
for (Integer threadNum : threadNumList) {
runTask(threadNum);
}
}
public static void runTask(int threadNum) throws ExecutionException, InterruptedException {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
threadNum, threadNum, 1, TimeUnit.SECONDS, new LinkedBlockingDeque<>(100)
);
long start = System.currentTimeMillis();
List<Future<?>> taskList = new ArrayList<>();
for (int i = 0; i < 55; i++) {
taskList.add(threadPoolExecutor.submit(() -> {
doJob();
}));
}
for (Future<?> future : taskList) {
future.get();
}
long time = System.currentTimeMillis() - start;
System.out.println(threadNum + "個線程,耗時:" + time);
threadPoolExecutor.shutdown();
}
public static Long doJob() {
long result = 0L;
for (int i = 0; i < Integer.MAX_VALUE; i++) {
if (i % 10_000_000 == 0) {
try {
LockSupport.parkNanos(100_000_000);
} catch (Exception ignore) {
}
}
result += i;
}
return result;
}
}執(zhí)行結(jié)果
4個線程,耗時:3670028個線程,耗時:19075116個線程,耗時:10835825個線程,耗時:7863234個線程,耗時:5315140個線程,耗時:5356345個線程,耗時:5519650個線程,耗時:55729
期間cpu占用情況如下

總結(jié)
1.線程數(shù)從4-34期間耗時基本上穩(wěn)步縮減,但是線程數(shù)從34變成50的時候耗時并沒有明顯減少,反而有增加的趨勢,只有cpu利用率一直在飆升。io密集型任務(wù)線程池任務(wù)的確有一個較優(yōu)解的,超過這個邊界再繼續(xù)增加線程數(shù),算力會被上下文切換給浪費掉,在執(zhí)行CPU密集型任務(wù)時這個現(xiàn)象會更加明顯。
2.即使是50個線程的時候,算力依然有剩余,并沒有達(dá)到100%利用率。這是因為,單個線程在活躍狀態(tài)下也并不能完全占用單個核心的所有時間片
3.每次任務(wù)執(zhí)行完都有一個小落差,這個可以自己思考一下為什么。
附
不同線程執(zhí)行耗時 以及資源利用率
34個線程,耗時:60389
35個線程,耗時:54077
36個線程,耗時:54886
37個線程,耗時:55035
38個線程,耗時:55231
39個線程,耗時:53961
40個線程,耗時:53701
41個線程,耗時:54406
42個線程,耗時:54794
43個線程,耗時:53585
44個線程,耗時:52690
45個線程,耗時:55242

最后
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
spring cloud gateway網(wǎng)關(guān)路由分配代碼實例解析
這篇文章主要介紹了spring cloud gateway網(wǎng)關(guān)路由分配代碼實例解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-01-01
SpringCloud?hystrix斷路器與局部降級全面介紹
什么是服務(wù)降級?當(dāng)服務(wù)器壓力劇增的情況下,根據(jù)實際業(yè)務(wù)情況及流量,對一些服務(wù)和頁面有策略的不處理或換種簡單的方式處理,從而釋放服務(wù)器資源以保證核心交易正常運(yùn)作或高效運(yùn)作2022-10-10
JDBC實現(xiàn)數(shù)據(jù)庫增刪改查功能
這篇文章主要為大家詳細(xì)介紹了JDBC實現(xiàn)數(shù)據(jù)庫增刪改查功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-07-07
Java中利用BitMap位圖實現(xiàn)海量級數(shù)據(jù)去重
有許多方法可以用來去重,比如使用列表、集合等等,但這些方法通常只適用于一般情況,然而,當(dāng)涉及到大量數(shù)據(jù)去重時,常見的 Java Set、List,甚至是 Java 8 的新特性 Stream 流等方式就顯得不太合適了,本文給大家介紹了Java中利用BitMap位圖實現(xiàn)海量級數(shù)據(jù)去重2024-04-04
SpringBoot根據(jù)注解動態(tài)執(zhí)行類中的方法實現(xiàn)
本文主要介紹了SpringBoot根據(jù)注解動態(tài)執(zhí)行類中的方法實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-08-08
Spring?Data?JPA系列JpaSpecificationExecutor用法詳解
這篇文章主要為大家介紹了Spring?Data?JPA系列JpaSpecificationExecutor用法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
Spring中@Transactional用法詳細(xì)介紹
這篇文章主要介紹了Spring中@Transactional用法詳細(xì)介紹的相關(guān)資料,需要的朋友可以參考下2017-02-02

