手寫一個輕量級動態(tài)線程池,很香!!
?? 歡迎準備 Java 面試以及學習 Java 的同學加入我的? 知識星球 ?,干貨很多!收費雖然是白菜價,但星球里的內(nèi)容或許比你參加上萬的培訓班質(zhì)量還要高。
??? 《Java 面試指北》 ?來啦!這是一份教你如何更高效地準備面試的小冊,涵蓋常見八股文(系統(tǒng)設計、常見框架、分布式、高并發(fā) ......)、優(yōu)質(zhì)面經(jīng)等內(nèi)容。
Java面試指南網(wǎng)站:javaguide.cn
在后臺開發(fā)中,會經(jīng)常用到線程池技術,對于線程池核心參數(shù)的配置很大程度上依靠經(jīng)驗。然而,由于系統(tǒng)運行過程中存在的不確定性,我們很難一勞永逸地規(guī)劃一個合理的線程池參數(shù)。在對線程池配置參數(shù)進行調(diào)整時,一般需要對服務進行重啟,這樣修改的成本就會偏高。一種解決辦法就是,將線程池的配置放到平臺側(cè),運行開發(fā)同學根據(jù)系統(tǒng)運行情況對核心參數(shù)進行動態(tài)配置。
本文以 Nacos 作為服務配置中心,以修改線程池核心線程數(shù)、最大線程數(shù)為例,實現(xiàn)一個簡單的動態(tài)化線程池。
說明:實際項目中,我們可直接使用現(xiàn)成的輪子比如 Hippo4J、Dynamic Tp,沒必要自己手動實現(xiàn)。這篇文章的目的主要是為了讓大家搞懂可以動態(tài)修改線程池參數(shù)配置的原理。
Hippo4J 我曾經(jīng)在「優(yōu)質(zhì) Java 開源項目推薦第 12 期」推薦過,Dynamic Tp 我曾經(jīng)在「優(yōu)質(zhì) Java 開源項目推薦第 13 期」推薦過。
代碼實現(xiàn)
1.依賴
<dependency>
????<groupId>com.alibaba.cloud</groupId>
????<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
????<version>2021.1</version>
</dependency>
<dependency>
????<groupId>com.alibaba.cloud</groupId>
????<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
????<version>2021.1</version>
</dependency>
<dependency>
????<groupId>org.springframework.boot</groupId>
????<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
????<groupId>org.springframework.boot</groupId>
????<artifactId>spring-boot-starter</artifactId>
</dependency>
2.配置 yml 文件
bootstrap.yml:
server:
??port:?8010
??#?應用名稱(nacos會將該名稱當做服務名稱)
spring:
??application:
????name:?order-service
??cloud:
????nacos:
??????discovery:
????????namespace:?public
????????server-addr:?192.168.174.129:8848
??????config:
????????server-addr:?192.168.174.129:8848
????????file-extension:?yml
application.yml:
spring:
??profiles:
????active:?dev
為什么要配置兩個 yml 文件?
springboot 中配置文件的加載是存在優(yōu)先級順序的,bootstrap 優(yōu)先級高于 application。
nacos 在項目初始化時,要保證先從配置中心進行配置拉取,拉取配置之后才能保證項目的正常啟動。
3.nacos 配置
登錄到 nacos 管理頁面,新建配置,如下圖所示:

注意 Data ID 的命名格式為,${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension},在本文中,Data ID 的名字就是order-service-dev.yml。

這里我們只配置了兩個參數(shù),核心線程數(shù)量和最大線程數(shù)。
4.線程池配置和 nacos 配置變更監(jiān)聽
@RefreshScope
@Configuration
public?class?DynamicThreadPool?implements?InitializingBean?{
????@Value("${core.size}")
????private?String?coreSize;
????@Value("${max.size}")
????private?String?maxSize;
????private?static?ThreadPoolExecutor?threadPoolExecutor;
????@Autowired
????private?NacosConfigManager?nacosConfigManager;
????@Autowired
????private?NacosConfigProperties?nacosConfigProperties;
????@Override
????public?void?afterPropertiesSet()?throws?Exception?{
????????//按照nacos配置初始化線程池
????????threadPoolExecutor?=?new?ThreadPoolExecutor(Integer.parseInt(coreSize),?Integer.parseInt(maxSize),?10L,?TimeUnit.SECONDS,
????????????????new?LinkedBlockingQueue<>(10),
????????????????new?ThreadFactoryBuilder().setNameFormat("c_t_%d").build(),
????????????????new?RejectedExecutionHandler()?{
????????????????????@Override
????????????????????public?void?rejectedExecution(Runnable?r,?ThreadPoolExecutor?executor)?{
????????????????????????System.out.println("rejected!");
????????????????????}
????????????????});
????????//nacos配置變更監(jiān)聽
????????nacosConfigManager.getConfigService().addListener("order-service-dev.yml",?nacosConfigProperties.getGroup(),
????????????????new?Listener()?{
????????????????????@Override
????????????????????public?Executor?getExecutor()?{
????????????????????????return?null;
????????????????????}
????????????????????@Override
????????????????????public?void?receiveConfigInfo(String?configInfo)?{
????????????????????????//配置變更,修改線程池配置
????????????????????????System.out.println(configInfo);
????????????????????????changeThreadPoolConfig(Integer.parseInt(coreSize),?Integer.parseInt(maxSize));
????????????????????}
????????????????});
????}
????/**
?????*?打印當前線程池的狀態(tài)
?????*/
????public?String?printThreadPoolStatus()?{
????????return?String.format("core_size:%s,thread_current_size:%s;"?+
????????????????????????"thread_max_size:%s;queue_current_size:%s,total_task_count:%s",?threadPoolExecutor.getCorePoolSize(),
????????????????threadPoolExecutor.getActiveCount(),?threadPoolExecutor.getMaximumPoolSize(),?threadPoolExecutor.getQueue().size(),
????????????????threadPoolExecutor.getTaskCount());
????}
????/**
?????*?給線程池增加任務
?????*
?????*?@param?count
?????*/
????public?void?dynamicThreadPoolAddTask(int?count)?{
????????for?(int?i?=?0;?i?<?count;?i++)?{
????????????int?finalI?=?i;
????????????threadPoolExecutor.execute(new?Runnable()?{
????????????????@Override
????????????????public?void?run()?{
????????????????????try?{
????????????????????????System.out.println(finalI);
????????????????????????Thread.sleep(10000);
????????????????????}?catch?(InterruptedException?e)?{
????????????????????????e.printStackTrace();
????????????????????}
????????????????}
????????????});
????????}
????}
????/**
?????*?修改線程池核心參數(shù)
?????*
?????*?@param?coreSize
?????*?@param?maxSize
?????*/
????private?void?changeThreadPoolConfig(int?coreSize,?int?maxSize)?{
????????threadPoolExecutor.setCorePoolSize(coreSize);
????????threadPoolExecutor.setMaximumPoolSize(maxSize);
????}
}
這個代碼就是實現(xiàn)動態(tài)線程池和核心了,需要說明的是:
-
@RefreshScope:這個注解用來支持 nacos 的動態(tài)刷新功能; -
@Value("${max.size}"),@Value("${core.size}"):這兩個注解用來讀取我們上一步在 nacos 配置的具體信息;同時,nacos 配置變更時,能夠?qū)崟r讀取到變更后的內(nèi)容 -
nacosConfigManager.getConfigService().addListener:配置監(jiān)聽,nacos 配置變更時實時修改線程池的配置。
5.controller
為了觀察線程池動態(tài)變更的效果,增加 Controller 類。
@RestController
@RequestMapping("/threadpool")
public?class?ThreadPoolController?{
????@Autowired
????private?DynamicThreadPool?dynamicThreadPool;
????/**
?????*?打印當前線程池的狀態(tài)
?????*/
????@GetMapping("/print")
????public?String?printThreadPoolStatus()?{
????????return?dynamicThreadPool.printThreadPoolStatus();
????}
????/**
?????*?給線程池增加任務
?????*
?????*?@param?count
?????*/
????@GetMapping("/add")
????public?String?dynamicThreadPoolAddTask(int?count)?{
????????dynamicThreadPool.dynamicThreadPoolAddTask(count);
????????return?String.valueOf(count);
????}
}
6.測試
啟動項目,訪問http://localhost:8010/threadpool/print打印當前線程池的配置。
圖片可以看到,這個就是我們之前在 nacos 配置的線程數(shù)。
訪問http://localhost:8010/threadpool/add?count=20增加20個任務,重新打印線程池配置
圖片可以看到已經(jīng)有線程在排隊了。
為了能夠看到效果,我們多訪問幾次/add 接口,增加任務數(shù),在控制臺出現(xiàn)拒絕信息時調(diào)整 nacos 配置。

此時,執(zhí)行/add 命令時,所有的線程都會提示 rejected。
調(diào)整 nacos 配置,將核心線程數(shù)調(diào)整為 50,最大線程數(shù)調(diào)整為 100.

重新多次訪問/add 接口增加任務,發(fā)現(xiàn)沒有拒絕信息了。這時,打印具體的線程狀態(tài),發(fā)現(xiàn)線程池參數(shù)修改成功。
圖片總結(jié)
這里,只是簡單實現(xiàn)了一個可以調(diào)整核心線程數(shù)和最大線程數(shù)的動態(tài)線程池。具體的線程池實現(xiàn)原理可以參考美團的這篇文章:https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html,結(jié)合監(jiān)控告警等實現(xiàn)一個完善的動態(tài)線程池產(chǎn)品。
優(yōu)秀的輪子還有好多,比如 Hippo4J ,使用起來和 dynamic-tp 差不多。Hippo4J 有無依賴中間件實現(xiàn)動靜線程池,也有默認實現(xiàn) Nacos 和 Apollo 的版本,而 dynamic-tp 默認實現(xiàn)依賴 Nacos 或 Apollo。
·········? END? ··············
?? 歡迎準備 Java 面試以及學習 Java 的同學加入我的 知識星球 ,干貨很多!收費雖然是白菜價,但星球里的內(nèi)容或許比你參加上萬的培訓班質(zhì)量還要高。
??? 《Java 面試指北》 來啦!這是一份教你如何更高效地準備面試的小冊,涵蓋常見八股文(系統(tǒng)設計、常見框架、分布式、高并發(fā) ......)、優(yōu)質(zhì)面經(jīng)等內(nèi)容。
近期文章精選?:
- 《JavaGuide 面試突擊版》 5.0 最新版下載
- 2023 秋招補錄&春招信息匯總,再沖一把!
- 31.2k!這是我見過最強的后臺管理系統(tǒng) ??!
- 面試 30 家公司,終于拿到 Offer !!
- 為什么說程序員是一個極度勞累的工作?
??如果本文對你有幫助的話,歡迎?點贊&在看&分享?,這對我繼續(xù)分享&創(chuàng)作優(yōu)質(zhì)文章非常重要。非常感謝!
