SpringBoot使用@Async異步注解

在項(xiàng)目中修赞,當(dāng)訪問(wèn)其他人的接口較慢或者做耗時(shí)任務(wù)時(shí)远舅,不想程序一直卡在耗時(shí)任務(wù)上缚忧,想程序能夠并行執(zhí)行,我們可以使用多線程來(lái)并行的處理任務(wù)专钉,這里介紹下 SpringBoot 下的 @Async 注解挑童,還有 ApplicationEventPublisher 可以了解下

代碼地址

1. Config

需要一個(gè)注解 @EnableAsync 開啟 @Async 的功能,SpringBoot 可以放在 Application 上跃须,也可以放其他配置文件上

@EnableAsync
@SpringBootApplication
public class Application { }

@Async 配置有兩個(gè)站叼,一個(gè)是執(zhí)行的線程池,一個(gè)是異常處理

執(zhí)行的線程池默認(rèn)情況下找唯一的 org.springframework.core.task.TaskExecutor菇民,或者一個(gè) Bean 的 Name 為 taskExecutor 的 java.util.concurrent.Executor 作為執(zhí)行任務(wù)的線程池尽楔。如果都沒(méi)有的話,會(huì)創(chuàng)建 SimpleAsyncTaskExecutor 線程池來(lái)處理異步方法調(diào)用第练,當(dāng)然 @Async 注解支持一個(gè) String 參數(shù)阔馋,來(lái)指定一個(gè) Bean 的 Name,類型是 Executor 或 TaskExecutor娇掏,表示使用這個(gè)指定的線程池來(lái)執(zhí)行這個(gè)異步任務(wù)

異常處理呕寝,@Async 標(biāo)記的方法只能是 void 或者 Future 返回值,在無(wú)返回值的異步調(diào)用中婴梧,異步處理拋出異常下梢,默認(rèn)是SimpleAsyncUncaughtExceptionHandler 的 handleUncaughtException() 會(huì)捕獲指定異常,只是簡(jiǎn)單的輸出了錯(cuò)誤日志(一般需要自定義配置異常處理)塞蹭,原有任務(wù)還會(huì)繼續(xù)運(yùn)行孽江,直到結(jié)束(具有 void 返回類型的方法不能將任何異常發(fā)送回調(diào)用者,默認(rèn)情況下此類未捕獲異常只會(huì)輸出錯(cuò)誤日志)浮还,而在有返回值的異步調(diào)用中竟坛,異步處理拋出了異常,會(huì)直接返回主線程處理钧舌,異步任務(wù)結(jié)束執(zhí)行担汤,主線程也會(huì)被異步方法中的異常中斷結(jié)束執(zhí)行

@Async有兩個(gè)使用的限制

  1. 它必須僅適用于 public 方法
  2. 在同一個(gè)類中調(diào)用異步方法將無(wú)法正常工作(self-invocation)
/**
 * AsyncConfig
 *
 * @author wliduo[i@dolyw.com]
 * @date 2020/5/19 17:58
 */
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    /**
     * logger
     */
    private static final Logger logger = LoggerFactory.getLogger(AsyncConfig.class);

    /**
     * 這里不實(shí)現(xiàn)了,使用 ThreadPoolConfig 里的線程池即可
     *
     * @param
     * @return java.util.concurrent.Executor
     * @throws
     * @author wliduo[i@dolyw.com]
     * @date 2020/5/19 18:00
     */
    /*@Override
    public Executor getAsyncExecutor() {
        return null;
    }*/

    /**
     * 只能捕獲無(wú)返回值的異步方法洼冻,有返回值的被主線程處理
     *
     * @param
     * @return org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler
     * @throws
     * @author wliduo[i@dolyw.com]
     * @date 2020/5/20 10:16
     */
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new CustomAsyncExceptionHandler();
    }

    /***
     * 處理異步方法中未捕獲的異常
     *
     * @author wliduo[i@dolyw.com]
     * @date 2020/5/19 19:16
     */
    class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {

        @Override
        public void handleUncaughtException(Throwable throwable, Method method, Object... obj) {
            logger.info("Exception message - {}", throwable.getMessage());
            logger.info("Method name - {}", method.getName());
            logger.info("Parameter values - {}", Arrays.toString(obj));
            if (throwable instanceof Exception) {
                Exception exception = (Exception) throwable;
                logger.info("exception:{}", exception.getMessage());
            }
            throwable.printStackTrace();
        }
    }
}
/**
 * 線程池配置
 *
 * @author wliduo
 * @date 2019/2/15 14:36
 */
@Configuration
public class ThreadPoolConfig {

    /**
     * logger
     */
    private final static Logger logger = LoggerFactory.getLogger(ThreadPoolConfig.class);

    @Value("${asyncThreadPool.corePoolSize}")
    private int corePoolSize;

    @Value("${asyncThreadPool.maxPoolSize}")
    private int maxPoolSize;

    @Value("${asyncThreadPool.queueCapacity}")
    private int queueCapacity;

    @Value("${asyncThreadPool.keepAliveSeconds}")
    private int keepAliveSeconds;

    @Value("${asyncThreadPool.awaitTerminationSeconds}")
    private int awaitTerminationSeconds;

    @Value("${asyncThreadPool.threadNamePrefix}")
    private String threadNamePrefix;

    /**
     * 線程池配置
     * @param
     * @return java.util.concurrent.Executor
     * @author wliduo
     * @date 2019/2/15 14:44
     */
    @Bean(name = "threadPoolTaskExecutor")
    public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
        logger.info("---------- 線程池開始加載 ----------");
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        // 核心線程池大小
        threadPoolTaskExecutor.setCorePoolSize(corePoolSize);
        // 最大線程數(shù)
        threadPoolTaskExecutor.setMaxPoolSize(maxPoolSize);
        // 隊(duì)列容量
        threadPoolTaskExecutor.setQueueCapacity(queueCapacity);
        // 活躍時(shí)間
        threadPoolTaskExecutor.setKeepAliveSeconds(keepAliveSeconds);
        // 主線程等待子線程執(zhí)行時(shí)間
        threadPoolTaskExecutor.setAwaitTerminationSeconds(awaitTerminationSeconds);
        // 線程名字前綴
        threadPoolTaskExecutor.setThreadNamePrefix(threadNamePrefix);
        // RejectedExecutionHandler:當(dāng)pool已經(jīng)達(dá)到max-size的時(shí)候崭歧,如何處理新任務(wù)
        // CallerRunsPolicy:不在新線程中執(zhí)行任務(wù),而是由調(diào)用者所在的線程來(lái)執(zhí)行
        threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 初始化
        threadPoolTaskExecutor.initialize();
        logger.info("---------- 線程池加載完成 ----------");
        return threadPoolTaskExecutor;
    }
}

2. Code

簡(jiǎn)單的使用撞牢,異步異常處理率碾,Service 方法異步,多個(gè) Service 方法異步屋彪,工具類異步

2.1. Controller

/**
 * AsyncController
 *
 * @author wliduo[i@dolyw.com]
 * @date 2020/5/19 14:46
 */
@RestController
@RequestMapping("/async")
public class AsyncController {

    /**
     * logger
     */
    private final static Logger logger = LoggerFactory.getLogger(AsyncController.class);

    @Autowired
    private AsyncService asyncService;

    @Autowired
    private SmsUtil smsUtil;

    /**
     * 可以看到無(wú)返回值異步方法出現(xiàn)異常所宰,主線程還是繼續(xù)執(zhí)行完成
     *
     * @param
     * @return void
     * @throws
     * @author wliduo[i@dolyw.com]
     * @date 2020/5/20 9:53
     */
    @GetMapping("/run1")
    public String run1() throws Exception {
        asyncService.task1();
        logger.info("run1開始執(zhí)行");
        Thread.sleep(5000);
        logger.info("run1執(zhí)行完成");
        return "run1 success";
    }

    /**
     * 可以看到有返回值異步方法出現(xiàn)異常,異常拋給主線程處理畜挥,導(dǎo)致主線程也被中斷執(zhí)行
     *
     * @param
     * @return java.lang.String
     * @throws
     * @author wliduo[i@dolyw.com]
     * @date 2020/5/20 10:15
     */
    @GetMapping("/run2")
    public String run2() throws Exception {
        Future<String> future = asyncService.task2();
        // get()方法阻塞主線程仔粥,直到執(zhí)行完成
        String result = future.get();
        logger.info("run2開始執(zhí)行");
        Thread.sleep(5000);
        logger.info("run2執(zhí)行完成");
        return result;
    }

    /**
     * 多個(gè)異步執(zhí)行
     *
     * @param
     * @return java.lang.String
     * @throws
     * @author wliduo[i@dolyw.com]
     * @date 2020/5/20 10:26
     */
    @GetMapping("/run3")
    public String run3() throws Exception {
        logger.info("run3開始執(zhí)行");
        long start = System.currentTimeMillis();
        Future<String> future3 = asyncService.task3();
        Future<String> future4 = asyncService.task4();
        // 這樣與下面是一樣的
        logger.info(future3.get());
        logger.info(future4.get());
        // 先判斷是否執(zhí)行完成
        boolean run3Done = Boolean.FALSE;
        while (true) {
            if (future3.isDone() && future4.isDone()) {
                // 執(zhí)行完成
                run3Done = Boolean.TRUE;
                break;
            }
            if (future3.isCancelled() || future4.isCancelled()) {
                // 取消情況
                break;
            }
        }
        if (run3Done) {
            logger.info(future3.get());
            logger.info(future4.get());
        } else {
            // 其他異常情況
        }
        long end = System.currentTimeMillis();
        logger.info("run3執(zhí)行完成,執(zhí)行時(shí)間: {}", end - start);
        return "run3 success";
    }

    /**
     * 工具類異步
     *
     * @param
     * @return java.lang.String
     * @throws
     * @author wliduo[i@dolyw.com]
     * @date 2020/5/20 10:59
     */
    @GetMapping("/sms")
    public String sms() throws Exception {
        logger.info("run1開始執(zhí)行");
        smsUtil.sendCode("15912347896", "135333");
        logger.info("run1執(zhí)行完成");
        return "send sms success";
    }
}

2.2. ServiceImpl

/**
 * AsyncServiceImpl
 *
 * @author wliduo[i@dolyw.com]
 * @date 2020/5/19 14:24
 */
@Service
public class AsyncServiceImpl implements AsyncService {

    /**
     * logger
     */
    private final static Logger logger = LoggerFactory.getLogger(AsyncServiceImpl.class);

    @Override
    @Async("threadPoolTaskExecutor")
    public void task1() throws Exception {
        logger.info("task1開始執(zhí)行");
        Thread.sleep(3000);
        logger.info("task1執(zhí)行結(jié)束");
        throw new RuntimeException("出現(xiàn)異常");
    }

    @Override
    @Async("threadPoolTaskExecutor")
    public Future<String> task2() throws Exception {
        logger.info("task2開始執(zhí)行");
        Thread.sleep(3000);
        logger.info("task2執(zhí)行結(jié)束");
        throw new RuntimeException("出現(xiàn)異常");
        // return new AsyncResult<String>("task2 success");
    }

    @Override
    @Async("threadPoolTaskExecutor")
    public Future<String> task3() throws Exception {
        logger.info("task3開始執(zhí)行");
        Thread.sleep(3000);
        logger.info("task3執(zhí)行結(jié)束");
        return new AsyncResult<String>("task3 success");
    }

    @Override
    @Async("threadPoolTaskExecutor")
    public Future<String> task4() throws Exception {
        logger.info("task4開始執(zhí)行");
        Thread.sleep(3000);
        logger.info("task4執(zhí)行結(jié)束");
        return new AsyncResult<String>("task4 success");
    }
}

2.3. SmsUtil

/**
 * SmsUtil
 *
 * @author wliduo[i@dolyw.com]
 * @date 2020/5/20 10:50
 */
@Component
public class SmsUtil {

    private static final Logger logger = LoggerFactory.getLogger(SmsUtil.class);

    /**
     * 異步發(fā)送短信
     *
     * @param phone
     * @param code
     * @return void
     * @throws
     * @author wliduo[i@dolyw.com]
     * @date 2020/5/20 10:53
     */
    @Async
    public void sendCode(String phone, String code) {
        logger.info("開始發(fā)送驗(yàn)證碼...");
        // 模擬調(diào)用接口發(fā)驗(yàn)證碼的耗時(shí)
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        logger.info("發(fā)送成功: {}", phone);
    }
    
}

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末躯泰,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子华糖,更是在濱河造成了極大的恐慌麦向,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件客叉,死亡現(xiàn)場(chǎng)離奇詭異诵竭,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)兼搏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門卵慰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人向族,你說(shuō)我怎么就攤上這事呵燕。” “怎么了件相?”我有些...
    開封第一講書人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵再扭,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我夜矗,道長(zhǎng)泛范,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任紊撕,我火速辦了婚禮罢荡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己区赵,他們只是感情好惭缰,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著笼才,像睡著了一般漱受。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上骡送,一...
    開封第一講書人閱讀 50,096評(píng)論 1 291
  • 那天昂羡,我揣著相機(jī)與錄音,去河邊找鬼摔踱。 笑死虐先,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的派敷。 我是一名探鬼主播蛹批,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼膀息!你這毒婦竟也來(lái)了般眉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤潜支,失蹤者是張志新(化名)和其女友劉穎甸赃,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冗酿,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡埠对,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了裁替。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片项玛。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖弱判,靈堂內(nèi)的尸體忽然破棺而出襟沮,到底是詐尸還是另有隱情,我是刑警寧澤昌腰,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布开伏,位于F島的核電站,受9級(jí)特大地震影響遭商,放射性物質(zhì)發(fā)生泄漏固灵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一劫流、第九天 我趴在偏房一處隱蔽的房頂上張望巫玻。 院中可真熱鬧丛忆,春花似錦、人聲如沸仍秤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)徒扶。三九已至粮彤,卻和暖如春根穷,著一層夾襖步出監(jiān)牢的瞬間姜骡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工屿良, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留圈澈,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓尘惧,卻偏偏與公主長(zhǎng)得像康栈,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子喷橙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351