SpringBoot 2.2.5 配置自定義線程池,并使用@Async執(zhí)行異步方法滨巴,@Scheduled實現(xiàn)定時任務(wù)思灌,及獲取線程池中線程的返回結(jié)果

說明

  1. 線程池是多線程的處理機制,線程池一般用于需要大量線程完成任務(wù)恭取,并且完成時間較短時使用泰偿,大量用于并發(fā)框架和異步執(zhí)行任務(wù)。

優(yōu)點

  1. 降低資源消耗秽荤,通過利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗
  2. 有利于線程的可控性,如果線程無休止創(chuàng)建柠横,會導(dǎo)致內(nèi)存耗盡窃款。
  3. 提高系統(tǒng)響應(yīng)速度,通過使用已存在的線程牍氛,不需要等待新線程的創(chuàng)建就可以立即執(zhí)行當(dāng)前任務(wù)晨继。

主要參數(shù)簡單解釋

  1. corePoolSize:核心線程數(shù),默認(rèn)的核心線程的1搬俊,向線程池提交一個任務(wù)時紊扬,如果線程池已經(jīng)創(chuàng)建的線程數(shù)小于核心線程數(shù),即使此時存在空閑線程唉擂,也會通過創(chuàng)建一個新線程來執(zhí)行新任務(wù)餐屎,知道創(chuàng)建的線程等于核心線程數(shù)時,如果有空閑線程玩祟,則使用空閑線程腹缩。
  2. maxPoolSize:最大線程數(shù),默認(rèn)的最大線程數(shù)為Integer.MAX_VALUE 即231-1空扎。當(dāng)隊列滿了之后
  3. keepAliveSeconds:允許線程空閑時間藏鹊,默認(rèn)的線程空閑時間為60秒,當(dāng)線程中的線程數(shù)大于核心線程數(shù)時转锈,線程的空閑時間如果超過線程的存活時間盘寡,則此線程會被銷毀,直到線程池中的線程數(shù)小于等于核心線程數(shù)時撮慨。
  4. queueCapacity:緩沖隊列數(shù)竿痰,默認(rèn)的緩沖隊列數(shù)是Integer.MAX_VALUE 即231-1,用于保存執(zhí)行任務(wù)的阻塞隊列
  5. allowCoreThreadTimeOut:銷毀機制砌溺,allowCoreThreadTimeOut為true則線程池數(shù)量最后銷毀到0個菇曲。allowCoreThreadTimeOut為false銷毀機制:超過核心線程數(shù)時,而且(超過最大值或者timeout過)抚吠,就會銷毀常潮。默認(rèn)是false

完整代碼地址在結(jié)尾!楷力!

第一步喊式,配置application.yml孵户,避免端口沖突

# 配置線程池
threadPoolTaskExecutor:
  corePoolSize: 10 # 核心線程數(shù)(默認(rèn)線程數(shù))
  maxPoolSize: 100 # 最大線程數(shù)
  keepAliveTime: 10 # 允許線程空閑時間(單位:默認(rèn)為秒)
  queueCapacity: 200 # 緩沖隊列數(shù)
  threadNamePrefix: custom-executor- # 線程名統(tǒng)一前綴

server:
  port: 8099

spring:
  application:
    name: threadpool-demo-server

第二步,創(chuàng)建ThreadPoolTaskExecutorConfig配置類岔留,如下

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

/**
 * @version 1.0
 * @author luoyu
 * @date 2019-08-09
 * @description 線程池配置
 */
@Configuration
@EnableAsync
@EnableScheduling
public class ThreadPoolTaskExecutorConfig {

    /**
     * 核心線程數(shù)(默認(rèn)線程數(shù))
     */
    @Value("${threadPoolTaskExecutor.corePoolSize}")
    private int corePoolSize;

    /**
     * 最大線程數(shù)
     */
    @Value("${threadPoolTaskExecutor.maxPoolSize}")
    private int maxPoolSize;

    /**
     * 允許線程空閑時間(單位:默認(rèn)為秒)
     */
    @Value("${threadPoolTaskExecutor.keepAliveTime}")
    private int keepAliveTime;

    /**
     * 緩沖隊列數(shù)
     */
    @Value("${threadPoolTaskExecutor.queueCapacity}")
    private int queueCapacity;

    /**
     * 線程池名前綴
     */
    @Value("${threadPoolTaskExecutor.threadNamePrefix}")
    private String threadNamePrefix;

    /**
     * @return ThreadPoolTaskExecutor
     * @author jinhaoxun
     * @description 線程池配置夏哭,bean的名稱,默認(rèn)為首字母小寫的方法名taskExecutor
     */
    @Bean("testTaskExecutor")
    public ThreadPoolTaskExecutor taskExecutor1() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //設(shè)置核心線程數(shù)
        executor.setCorePoolSize(corePoolSize);
        //設(shè)置最大線程數(shù)
        executor.setMaxPoolSize(maxPoolSize);
        //線程池所使用的緩沖隊列
        executor.setQueueCapacity(queueCapacity);
        //等待任務(wù)在關(guān)機時完成--表明等待所有線程執(zhí)行完
        executor.setWaitForTasksToCompleteOnShutdown(true);
        // 等待時間 (默認(rèn)為0献联,此時立即停止)竖配,并沒等待xx秒后強制停止
        executor.setKeepAliveSeconds(keepAliveTime);
        // 線程名稱前綴
        executor.setThreadNamePrefix(threadNamePrefix);
        // 線程池對拒絕任務(wù)的處理策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 初始化
        executor.initialize();
        return executor;
    }

}

說明

  1. @EnableAsync開啟@Async注解支持,也可以添加在啟動類上
  2. @EnableScheduling開啟@Scheduled注解支持里逆,可以使用線程池配置定時任務(wù)进胯,也可以添加在啟動類上

第三步,創(chuàng)建類服務(wù)類原押,TestService胁镐,TestServiceImpl,如下

TestService

public interface TestService {

    void test1();

    void test2();

    void test3();

    void test4();

}

TestServiceImpl

import com.luoyu.threadpool.service.TestService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Slf4j
@Service
public class TestServiceImpl implements TestService {

    @Resource(name = "testTaskExecutor")
    private ThreadPoolTaskExecutor testTaskExecutor;

    // 定時任務(wù)诸衔,一秒執(zhí)行一次
    @Scheduled(fixedRate  = 1000)
    @Override
    public void test1() {
        log.info("定時任務(wù)盯漂,一秒執(zhí)行一次");
    }

    @Override
    public void test2() {
        log.info("看看是哪個線程執(zhí)行了我!");
    }

    @Override
    public void test3() {
        testTaskExecutor.execute(() -> {
            log.info("看看是哪個線程執(zhí)行了我笨农!");
        });
    }

    @Async("testTaskExecutor")
    @Override
    public void test4() {
        log.info("看看是哪個線程執(zhí)行了我就缆!");
    }

}

第四步,創(chuàng)建類單元測試類谒亦,ThreadpoolApplicationTests违崇,并進行測試,如下

import com.luoyu.threadpool.service.TestService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@Slf4j
// 獲取啟動類诊霹,加載配置羞延,確定裝載 Spring 程序的裝載方法,它回去尋找 主配置啟動類(被 @SpringBootApplication 注解的)
@SpringBootTest
class ThreadpoolApplicationTests {

    @Autowired
    private TestService testService;

    @Test
    void test2(){
        testService.test2();
    }

    @Test
    void test3(){
        testService.test3();
    }

    @Test
    void test4(){
        testService.test4();

    }

    @BeforeEach
    void testBefore(){
        log.info("測試開始!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
    }

    @AfterEach
    void testAfter(){
        log.info("測試結(jié)束!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
    }

}

第五步脾还,測試定時任務(wù)的話伴箩,只需要啟動項目,查看控制臺日志即可

注意鄙漏,@Async注解失效可能原因

  1. 沒有在@SpringBootApplication啟動類當(dāng)中添加注解@EnableAsync注解
  2. 異步方法使用注解@Async的返回值只能為void或者Future
  3. 沒有走Spring的代理類嗤谚。因為@Transactional和@Async注解的實現(xiàn)都是基于Spring的AOP,而AOP的實現(xiàn)是基于動態(tài)代理模式實現(xiàn)的怔蚌。那么注解失效的原因就很明顯了巩步,有可能因為調(diào)用方法的是對象本身而不是代理對象,因為沒有經(jīng)過Spring容器

第六步桦踊,獲取線程池中線程的返回結(jié)果椅野,修改TestService,TestServiceImpl新增方法,如下

TestService

public interface TestService {

    void test1();

    void test2();

    void test3();

    void test4();

    void test5() throws Exception;

}

TestServiceImpl

import com.luoyu.threadpool.service.TestService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.concurrent.Future;

@Slf4j
@Service
public class TestServiceImpl implements TestService {

    @Resource(name = "testTaskExecutor")
    private ThreadPoolTaskExecutor testTaskExecutor;

    // 定時任務(wù)竟闪,一秒執(zhí)行一次
    @Scheduled(fixedRate  = 1000)
    @Override
    public void test1() {
        log.info("定時任務(wù)离福,一秒執(zhí)行一次,看看是哪個線程執(zhí)行了我炼蛤!{}", Thread.currentThread().getName());
    }

    @Override
    public void test2() {
        log.info("看看是哪個線程執(zhí)行了我妖爷!{}", Thread.currentThread().getName());
    }

    @Override
    public void test3() {
        for (int i = 0; i < 10; i++) {
            testTaskExecutor.execute(() -> {
                log.info("看看是哪個線程執(zhí)行了我!{}", Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
    }

    @Async("testTaskExecutor")
    @Override
    public void test4() {
        log.info("看看是哪個線程執(zhí)行了我理朋!{}", Thread.currentThread().getName());
    }

    @Override
    public void test5() throws Exception {
        // 啟動兩個線程執(zhí)行子任務(wù)
        Future<Integer> count1 = testTaskExecutor.submit(() -> this.getCount1());
        Future<Integer> count2 = testTaskExecutor.submit(() -> this.getCount2());

        // 此處主線程進行阻塞
        Integer integer1 = count1.get();
        Integer integer2 = count2.get();

        // 拿到子線程返回結(jié)果
        log.info("1:" + integer1 + "絮识,2:" + integer2);
    }
    
    private Integer getCount1() throws InterruptedException {
        Thread.sleep(5000);
        return 50;
    }

    private Integer getCount2() throws InterruptedException {
        Thread.sleep(3000);
        return 30;
    }

}

第七步,修改單元測試類嗽上,ThreadpoolApplicationTests次舌,新增測試方法并進行測試,如下

import com.luoyu.threadpool.service.TestService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@Slf4j
// 獲取啟動類炸裆,加載配置垃它,確定裝載 Spring 程序的裝載方法鲜屏,它回去尋找 主配置啟動類(被 @SpringBootApplication 注解的)
@SpringBootTest
class ThreadpoolApplicationTests {

    @Autowired
    private TestService testService;

    @Test
    void test2(){
        testService.test2();
    }

    @Test
    void test3(){
        testService.test3();
    }

    @Test
    void test4(){
        testService.test4();
    }

    @Test
    void test5() throws Exception {
        testService.test5();
    }

    @BeforeEach
    void testBefore(){
        log.info("測試開始!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
    }

    @AfterEach
    void testAfter(){
        log.info("測試結(jié)束!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
    }

}

完整代碼地址:https://github.com/Jinhx128/springboot-demo

注:此工程包含多個module烹看,本文所用代碼均在threadpool-demo模塊下

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市洛史,隨后出現(xiàn)的幾起案子惯殊,更是在濱河造成了極大的恐慌,老刑警劉巖也殖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件土思,死亡現(xiàn)場離奇詭異,居然都是意外死亡忆嗜,警方通過查閱死者的電腦和手機己儒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來捆毫,“玉大人闪湾,你說我怎么就攤上這事〖保” “怎么了途样?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長濒憋。 經(jīng)常有香客問我何暇,道長,這世上最難降的妖魔是什么凛驮? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任裆站,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘遏插。我一直安慰自己捂贿,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布胳嘲。 她就那樣靜靜地躺著厂僧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪了牛。 梳的紋絲不亂的頭發(fā)上颜屠,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音鹰祸,去河邊找鬼甫窟。 笑死,一個胖子當(dāng)著我的面吹牛蛙婴,可吹牛的內(nèi)容都是我干的粗井。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼街图,長吁一口氣:“原來是場噩夢啊……” “哼浇衬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起餐济,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤耘擂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后絮姆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體醉冤,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年篙悯,在試婚紗的時候發(fā)現(xiàn)自己被綠了蚁阳。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡鸽照,死狀恐怖螺捐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情移宅,我是刑警寧澤归粉,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站漏峰,受9級特大地震影響糠悼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜浅乔,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一倔喂、第九天 我趴在偏房一處隱蔽的房頂上張望铝条。 院中可真熱鬧,春花似錦席噩、人聲如沸班缰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽埠忘。三九已至,卻和暖如春馒索,著一層夾襖步出監(jiān)牢的瞬間莹妒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工绰上, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留旨怠,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓蜈块,卻偏偏與公主長得像鉴腻,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子百揭,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355