spring boot:使用多個線程池實現(xiàn)實現(xiàn)任務(wù)的線程池隔離(spring boot 2.3.2)

一,為什么要使用多個線程池?

使用多個線程池,
把相同的任務(wù)放到同一個線程池中冰蘑,
可以起到隔離的作用涌韩,避免有線程出錯時影響到其他線程池,
例如只有一個線程池時潦蝇,
有兩種任務(wù),下單,處理圖片证舟,
如果線程池被處理圖片的任務(wù)占滿,影響下單任務(wù)的進行

說明:劉宏締的架構(gòu)森林是一個專注架構(gòu)的博客窗骑,地址:https://www.cnblogs.com/architectforest

對應(yīng)的源碼可以訪問這里獲扰稹: https://github.com/liuhongdi/

二,演示項目的相關(guān)信息

1,項目地址:

https://github.com/liuhongdi/multithreadpool

2,項目功能說明:

創(chuàng)建了兩個線程池创译,

一個負責(zé)發(fā)郵件抵知,

另一個負責(zé)處理圖片

實際演示中都是sleep

3,項目結(jié)構(gòu):如圖:

image

三,java代碼說明:

1,ThreadPoolConfig.java


@Configuration
@EnableAsync public class ThreadPoolConfig { //用來生成縮略圖的線程池
    @Bean(name = "imageThreadPool") public ThreadPoolTaskExecutor imageThreadPool() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 設(shè)置核心線程數(shù),它是可以同時被執(zhí)行的線程數(shù)量
        executor.setCorePoolSize(2); // 設(shè)置最大線程數(shù),緩沖隊列滿了之后會申請超過核心線程數(shù)的線程
        executor.setMaxPoolSize(10); // 設(shè)置緩沖隊列容量,在執(zhí)行任務(wù)之前用于保存任務(wù)
        executor.setQueueCapacity(50); // 設(shè)置線程生存時間(秒),當(dāng)超過了核心線程出之外的線程在生存時間到達之后會被銷毀
        executor.setKeepAliveSeconds(60); // 設(shè)置線程名稱前綴
        executor.setThreadNamePrefix("imagePool-"); // 設(shè)置拒絕策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 等待所有任務(wù)結(jié)束后再關(guān)閉線程池
        executor.setWaitForTasksToCompleteOnShutdown(true); //初始化
 executor.initialize(); return executor;
    } //用來發(fā)郵件的線程池
    @Bean(name = "emailThreadPool") public ThreadPoolTaskExecutor emailThreadPool() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 設(shè)置核心線程數(shù),它是可以同時被執(zhí)行的線程數(shù)量
        executor.setCorePoolSize(2); // 設(shè)置最大線程數(shù),緩沖隊列滿了之后會申請超過核心線程數(shù)的線程
        executor.setMaxPoolSize(10); // 設(shè)置緩沖隊列容量,在執(zhí)行任務(wù)之前用于保存任務(wù)
        executor.setQueueCapacity(50); // 設(shè)置線程生存時間(秒),當(dāng)超過了核心線程出之外的線程在生存時間到達之后會被銷毀
        executor.setKeepAliveSeconds(60); // 設(shè)置線程名稱前綴
        executor.setThreadNamePrefix("emailPool-"); // 設(shè)置拒絕策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 等待所有任務(wù)結(jié)束后再關(guān)閉線程池
        executor.setWaitForTasksToCompleteOnShutdown(true); //初始化
 executor.initialize(); return executor;
    }
}

說明:配置要使用的線程池软族,按業(yè)務(wù)類型區(qū)分開,

注意命名:一個命名為:emailThreadPool

一個命名為:imageThreadPool

另外注意線程池使用了不同的前綴刷喜,使實際運行時區(qū)分

2,HomeController.java

@RequestMapping("/home")
@Controller public class HomeController {
    @Resource private MailService mailService;

    @Resource private ImageService imageService;

    @Resource private ThreadPoolTaskExecutor imageThreadPool; //監(jiān)控線程池的狀態(tài), //我們得到的數(shù)字,只是大體接近立砸,并不是嚴格的準確數(shù)字
    @GetMapping("/poolstatus")
    @ResponseBody public String poolstatus() {
        String statusStr = ""; int queueSize = imageThreadPool.getThreadPoolExecutor().getQueue().size();
        statusStr +="當(dāng)前排隊線程數(shù):" + queueSize; int activeCount = imageThreadPool.getThreadPoolExecutor().getActiveCount();
        statusStr +="當(dāng)前活動線程數(shù):" + activeCount; long completedTaskCount = imageThreadPool.getThreadPoolExecutor().getCompletedTaskCount();
        statusStr +="執(zhí)行完成線程數(shù):" + completedTaskCount; long taskCount = imageThreadPool.getThreadPoolExecutor().getTaskCount();
        statusStr +="總線程數(shù):" + taskCount; return statusStr;
    } //異步發(fā)送一封注冊成功的郵件
    @GetMapping("/asyncmail")
    @ResponseBody public String regMail() {
        mailService.sendHtmlMail(); return "mail sended";
    } //異步執(zhí)行sleep1秒10次
    @GetMapping("/asyncimage")
    @ResponseBody public Map<String, Object> asyncsleep() throws ExecutionException, InterruptedException { long start = System.currentTimeMillis();
        Map<String, Object> map = new HashMap<>();
        List<Future<String>> futures = new ArrayList<>(); for (int i = 0; i < 50; i++) {
            Future<String> future = imageService.asynctmb(i);
            futures.add(future);
        }
        List<String> response = new ArrayList<>(); for (Future future : futures) {
            String string = (String) future.get();
            response.add(string);
        }
        map.put("data", response);
        map.put("消耗時間", String.format("任務(wù)執(zhí)行成功,耗時{%s}毫秒", System.currentTimeMillis() - start)); return map;
    }
}

3,MailServiceImpl.java

@Service public class MailServiceImpl  implements MailService { 
  private Logger logger= LoggerFactory.getLogger(MailServiceImpl.class);

    @Resource private MailUtil mailUtil; //異步發(fā)送html格式的郵件掖疮,演示時只是sleep1秒
    @Async(value="emailThreadPool")
    @Override public void sendHtmlMail() {
         logger.info("sendHtmlMail begin"); try {
            Thread.sleep(2000);    //延時1秒
 } catch(InterruptedException e) {
            e.printStackTrace();
        }
    }
}

說明:Async注解指定線程池的名字是:emailThreadPool

4,ImageServiceImpl.java

@Service public class ImageServiceImpl implements ImageService { private Logger logger= LoggerFactory.getLogger(MailServiceImpl.class); //演示處理圖片,只是sleep1秒
    @Async(value="imageThreadPool")
    @Override public Future<String> asynctmb(int i) {
        logger.info("asynctmb begin");
        String start= TimeUtil.getMilliTimeNow(); try {
            Thread.sleep(1000);    //延時1秒
 } catch(InterruptedException e) {
            e.printStackTrace();
        } //log.info("async function sleep   end");
        String end=TimeUtil.getMilliTimeNow(); return new AsyncResult<>(String.format("asynctmb方法,第 %s 個線程:開始時間:%s,結(jié)束時間:%s",i,start,end));
    }
}

說明:Async注解指定線程池的名字是:imageThreadPool

四颗祝,測試效果:

1,測試一個線程:訪問:

http://127.0.0.1:8080/home/asyncmail

查看控制臺:

2020-08-10 14:54:35.671  INFO 2570 --- [    emailPool-1] c.m.demo.service.impl.MailServiceImpl    : sendHtmlMail begin

可以看到線程的前綴是emailThreadPool的線程的前綴

2,測試多個線程:訪問:

http://127.0.0.1:8080/home/asyncimage

可以看到返回信息:

...
"消耗時間":"任務(wù)執(zhí)行成功,耗時{25052}毫秒"

執(zhí)行時每次并發(fā)的線程數(shù)是2浊闪,一共創(chuàng)建了50個線程,

每個線程sleep用時1秒

所以共用時25秒螺戳,

3,查看線程池狀態(tài):訪問:

http://127.0.0.1:8080/home/asyncimage

同時訪問:

http://127.0.0.1:8080/home/poolstatus

可以看到返回的狀態(tài)信息:

當(dāng)前排隊線程數(shù):44當(dāng)前活動線程數(shù):2執(zhí)行完成線程數(shù):54總線程數(shù):100

說明:ThreadPoolExecutor中的統(tǒng)計信息只是近似值搁宾,不是完全準確的數(shù)字

進階的動態(tài)注冊線程池


    @Value("${service.short.names}")
    private String serviceShortNames;

    @Bean
    public void initExecutors() {
        ConfigurableApplicationContext applicationContext = (ConfigurableApplicationContext) ApplicationContextHelper.getApplicationContext();
        DefaultListableBeanFactory autowireCapableBeanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();

        for (String serviceName : serviceShortNames.split(",")) {
            BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(VisiableThreadPoolTaskExecutor.class);

            beanDefinitionBuilder.addPropertyValue("corePoolSize", CORE_POOL_SIZE);
            beanDefinitionBuilder.addPropertyValue("maxPoolSize", MAX_POOL_SIZE);
            beanDefinitionBuilder.addPropertyValue("queueCapacity", QUEUE_CAPACITY);
            beanDefinitionBuilder.addPropertyValue("keepAliveSeconds", KEEP_ALIVE_SECONDS);
            beanDefinitionBuilder.addPropertyValue("threadNamePrefix", THREAD_NAME_PREFIX + serviceName + "-");
            beanDefinitionBuilder.addPropertyValue("waitForTasksToCompleteOnShutdown", WAIT_FOR_TASKS_TO_COMPLETE_ON_SHUTDOWN);
            beanDefinitionBuilder.setLazyInit(false);

            String beanName = serviceName + ASYNC_EXECUTOR_SUFFIX;
            beanDefinitionBuilder.setScope(ConfigurableBeanFactory.SCOPE_SINGLETON);  // 默認單例
            autowireCapableBeanFactory.registerBeanDefinition(beanName, beanDefinitionBuilder.getBeanDefinition());
        }
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市倔幼,隨后出現(xiàn)的幾起案子盖腿,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件翩腐,死亡現(xiàn)場離奇詭異鸟款,居然都是意外死亡,警方通過查閱死者的電腦和手機茂卦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進店門欠雌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人疙筹,你說我怎么就攤上這事富俄。” “怎么了而咆?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵霍比,是天一觀的道長。 經(jīng)常有香客問我暴备,道長悠瞬,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任涯捻,我火速辦了婚禮浅妆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘障癌。我一直安慰自己凌外,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布涛浙。 她就那樣靜靜地躺著康辑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪轿亮。 梳的紋絲不亂的頭發(fā)上疮薇,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天,我揣著相機與錄音我注,去河邊找鬼按咒。 笑死,一個胖子當(dāng)著我的面吹牛但骨,可吹牛的內(nèi)容都是我干的励七。 我是一名探鬼主播,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼嗽冒,長吁一口氣:“原來是場噩夢啊……” “哼呀伙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起添坊,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎箫锤,沒想到半個月后贬蛙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體雨女,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年阳准,在試婚紗的時候發(fā)現(xiàn)自己被綠了氛堕。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡野蝇,死狀恐怖讼稚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情绕沈,我是刑警寧澤锐想,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站乍狐,受9級特大地震影響赠摇,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜浅蚪,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一藕帜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧惜傲,春花似錦洽故、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至浊伙,卻和暖如春撞秋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背嚣鄙。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工吻贿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人哑子。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓舅列,卻偏偏與公主長得像,于是被迫代替她去往敵國和親卧蜓。 傳聞我的和親對象是個殘疾皇子帐要,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,507評論 2 359

推薦閱讀更多精彩內(nèi)容