IO密集型任務(wù)設(shè)置線程池線程數(shù)實(shí)現(xiàn)方式
任務(wù)類型
CPU密集
CPU密集型的話,一般配置CPU處理器個(gè)數(shù)+/-1個(gè)線程,所謂CPU密集型就是指系統(tǒng)大部分時(shí)間是在做程序正常的計(jì)算任務(wù),例如數(shù)字運(yùn)算、賦值、分配內(nèi)存、內(nèi)存拷貝、循環(huán)、查找、排序等,這些處理都需要CPU來完成。
IO密集
IO密集型的話,是指系統(tǒng)大部分時(shí)間在跟I/O交互,而這個(gè)時(shí)間線程不會占用CPU來處理,即在這個(gè)時(shí)間范圍內(nèi),可以由其他線程來使用CPU,因而可以多配置一些線程。(線程處于io等待或則阻塞狀態(tài)時(shí),不會占用CPU資源)
混合型
混合型的話,是指兩者都占有一定的時(shí)間。
實(shí)際上工作中的大部分場景中,線程池的能力往往會超出想象。
測試準(zhǔn)備
下面的計(jì)算方式很粗略,而且有漏洞,但是也可以作為一個(gè)參考
處理器信息
四核8線程 (超線程)
任務(wù)示例
我們首先確認(rèn)一下單個(gè)任務(wù)的io時(shí)間占比,下面是測試代碼
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 + "個(gè)線程,耗時(shí):" + 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個(gè)線程,耗時(shí):27862停頓占比0.7716603258918958
也就是說大概77%的時(shí)間線程在睡覺。
分析
按我電腦的配置可以認(rèn)為核心數(shù)coreNum為8, 假設(shè)任務(wù)數(shù)夠多的情況下。
不考慮上下文切換等的耗時(shí),單個(gè)任務(wù)io耗時(shí)占比為x,在線程數(shù)最少的情況下想讓cpu利用率達(dá)到最高,可以得出一個(gè)等式 1 / (1 - x) * coreNum = 100% * coreNum(我們假設(shè)線程在活躍狀態(tài)時(shí)能完全占用單個(gè)核心)。
代入上面得到的值 x = 0.77, coreNum = 8 可以比較容易的算出來如果想讓cpu利用率達(dá)到最高, 1 / (1 - 0.77) * 8 約等于34。即線程池的線程數(shù)設(shè)置為35比較合理。
在我的電腦上合適的線程數(shù)和任務(wù)io耗時(shí)占比x的關(guān)系大致可以認(rèn)為 1 / (1 - x) * 8,即理論上的圖如下,這是一個(gè)非常粗糙的等式,實(shí)際上隨著線程數(shù)增多,上下文切換帶來的開銷越來越大,和下面這張圖的出入還是蠻大的。
不同線程數(shù)下的程序總執(zhí)行耗時(shí)
下面簡單修改下程序來驗(yàn)證一下任務(wù)量固定,不同線程數(shù)下的程序執(zhí)行耗時(shí)。
代碼
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 + "個(gè)線程,耗時(shí):" + 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個(gè)線程,耗時(shí):3670028個(gè)線程,耗時(shí):19075116個(gè)線程,耗時(shí):10835825個(gè)線程,耗時(shí):7863234個(gè)線程,耗時(shí):5315140個(gè)線程,耗時(shí):5356345個(gè)線程,耗時(shí):5519650個(gè)線程,耗時(shí):55729
期間cpu占用情況如下
總結(jié)
1.線程數(shù)從4-34期間耗時(shí)基本上穩(wěn)步縮減,但是線程數(shù)從34變成50的時(shí)候耗時(shí)并沒有明顯減少,反而有增加的趨勢,只有cpu利用率一直在飆升。io密集型任務(wù)線程池任務(wù)的確有一個(gè)較優(yōu)解的,超過這個(gè)邊界再繼續(xù)增加線程數(shù),算力會被上下文切換給浪費(fèi)掉,在執(zhí)行CPU密集型任務(wù)時(shí)這個(gè)現(xiàn)象會更加明顯。
2.即使是50個(gè)線程的時(shí)候,算力依然有剩余,并沒有達(dá)到100%利用率。這是因?yàn)?,單個(gè)線程在活躍狀態(tài)下也并不能完全占用單個(gè)核心的所有時(shí)間片
3.每次任務(wù)執(zhí)行完都有一個(gè)小落差,這個(gè)可以自己思考一下為什么。
附
不同線程執(zhí)行耗時(shí) 以及資源利用率
34個(gè)線程,耗時(shí):60389
35個(gè)線程,耗時(shí):54077
36個(gè)線程,耗時(shí):54886
37個(gè)線程,耗時(shí):55035
38個(gè)線程,耗時(shí):55231
39個(gè)線程,耗時(shí):53961
40個(gè)線程,耗時(shí):53701
41個(gè)線程,耗時(shí):54406
42個(gè)線程,耗時(shí):54794
43個(gè)線程,耗時(shí):53585
44個(gè)線程,耗時(shí):52690
45個(gè)線程,耗時(shí):55242
最后
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java的Struts2框架配合Ext JS處理JSON數(shù)據(jù)的使用示例
這篇文章主要介紹了Java的Struts2框架配合Ext JS處理JSON數(shù)據(jù)的使用示例,包括將Ext JS中的JSON數(shù)據(jù)解析為列表的方法,需要的朋友可以參考下2016-03-03java面試題——詳解HashMap和Hashtable 的區(qū)別
本篇文章主要介紹了java中HashMap和Hashtable的區(qū)別,具有一定的參考價(jià)值,有需要的可以了解一下。2016-11-11SpringBoot3 響應(yīng)式網(wǎng)絡(luò)請求客戶端的實(shí)現(xiàn)
本文主要介紹了SpringBoot3 響應(yīng)式網(wǎng)絡(luò)請求客戶端的實(shí)現(xiàn),文章詳細(xì)闡述了如何使用SpringBoot3的網(wǎng)絡(luò)請求客戶端進(jìn)行HTTP請求和處理響應(yīng),并提供了示例代碼和說明,具有一定的參考價(jià)值,感興趣的可以了解一下2023-08-08SpringBoot的DeferredResult案例:DeferredResult的超時(shí)處理方式
這篇文章主要介紹了SpringBoot的DeferredResult案例:DeferredResult的超時(shí)處理方式,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-01-01springboot的統(tǒng)一異常處理,使用@RestControllerAdvice詳解
@RestControllerAdvice是Spring Boot中的全局異常處理注解,結(jié)合了@ControllerAdvice和@ResponseBody的功能,通過創(chuàng)建自定義異常類和全局異常處理器,可以實(shí)現(xiàn)統(tǒng)一異常處理,確保API的一致性和響應(yīng)的標(biāo)準(zhǔn)化2024-12-12Java8新特性之深入解析日期和時(shí)間_動力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了Java8新特性之深入解析日期和時(shí)間_動力節(jié)點(diǎn)Java學(xué)院整理,需要的朋友可以參考下2017-06-06