Spring Boot 中@Aynsc的用法詳解

Spring Boot 中@Aynsc的用法

1. 啟用Aynsc

1.1 添加pom依賴

 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
</dependency>

1.2 在啟動類添加@EnableAsync注解

@SpringBootApplication
@EnableAsync
public class AsyncApplication {
    public static void main(String[] args) {
        SpringApplication.run(AsyncApplication.class, args);
    }
}

1.3 在service中使用@Async注解

這里我們用一個用戶注冊并發(fā)送郵件的場景來說明

controller

@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {

    @Autowired
    private UserService userService;

    @Autowired
    private MailService mailService;

    @GetMapping("/register")
    public String register(){
        try {
            long start = System.currentTimeMillis();
            userService.insert();
            mailService.sendMail();
            log.info("執(zhí)行時間"+(System.currentTimeMillis()-start));
            return "注冊成功";
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "注冊失敗";
    }
}

UserService

@Service
@Slf4j
public class UserService {

    public void insert(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("添加用戶成功");
    }
}

MailService

@Service
@Slf4j
public class MailService {
    
    @Async
    public void sendMail(){
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("發(fā)送郵件成功");
    }
}

正常如果不使用異步調(diào)用的方式時,我們的注冊流程耗時應(yīng)該是11000+ms

使用異步以后耗時情況

2018-11-23 09:44:27.462  INFO 2600 --- [nio-8080-exec-1] com.example.async.service.UserService    : 添加用戶成功
2018-11-23 09:44:27.470  INFO 2600 --- [nio-8080-exec-1] c.e.async.controller.UserController      : 執(zhí)行時間1009
2018-11-23 09:44:37.478  INFO 2600 --- [         task-1] com.example.async.service.MailService    : 發(fā)送郵件成功

可以看到耗時只有1009ms

2. 帶有返回值的異步

這時候注冊流程發(fā)生了變化,注冊的同時要通過外部系統(tǒng)獲取用戶的信息更新到本地數(shù)據(jù)庫

修改controller

@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {

    @Autowired
    private UserService userService;

    @Autowired
    private MailService mailService;

    @Autowired
    private OuterService outerService;

    @GetMapping("/register")
    public String register(){
        try {
            long start = System.currentTimeMillis();
            Future<String> userInfoFuture = outerService.getUserInfo();
            userService.insert();
            mailService.sendMail();
            String userInfo = userInfoFuture.get();
            userService.update(userInfo);
            log.info("執(zhí)行時間"+(System.currentTimeMillis()-start));
            return "注冊成功";
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "注冊失敗";
    }
}

OuterService

@Service
@Slf4j
public class OuterService {

    @Async
    public Future<String> getUserInfo(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            log.error("獲取用戶詳情失敗",e);
        }
        log.info("獲取用戶詳情成功");
        return new AsyncResult<>("success");
    }
}

執(zhí)行日志

2018-11-23 10:04:01.353  INFO 9240 --- [nio-8080-exec-2] com.example.async.service.UserService    : 添加用戶成功
2018-11-23 10:04:01.356  INFO 9240 --- [         task-1] com.example.async.service.OuterService   : 獲取用戶詳情成功
2018-11-23 10:04:01.358  INFO 9240 --- [nio-8080-exec-2] com.example.async.service.UserService    : 更新用戶成功
2018-11-23 10:04:01.358  INFO 9240 --- [nio-8080-exec-2] c.e.async.controller.UserController      : 執(zhí)行時間1010
2018-11-23 10:04:11.362  INFO 9240 --- [         task-2] com.example.async.service.MailService    : 發(fā)送郵件成功

3. 異常處理

3.1 帶有返回值的異常處理

當一個@Async方法有一個Future類型的返回值時,在調(diào)Future的get()方法獲取任務(wù)的執(zhí)行結(jié)果時拋出的異常。

修改OuterService

@Service
@Slf4j
public class OuterService {

    @Async
    public Future<String> getUserInfo() throws Exception {
        try {
            Thread.sleep(1000);
            throw new Exception("獲取用戶詳情異常");
        } catch (InterruptedException e) {
            log.error("獲取用戶詳情失敗",e);
        }
        log.info("獲取用戶詳情成功");
        return new AsyncResult<>("success");
    }
}

執(zhí)行結(jié)果

2018-11-23 10:49:26.085  INFO 3752 --- [nio-8080-exec-1] com.example.async.service.UserService    : 添加用戶成功
2018-11-23 10:49:26.106 ERROR 3752 --- [nio-8080-exec-1] c.e.async.controller.UserController      : java.lang.Exception: 獲取用戶詳情異常
2018-11-23 10:49:36.090  INFO 3752 --- [         task-2] com.example.async.service.MailService    : 發(fā)送郵件成功

我們看到添加和發(fā)送郵件成功了 在String userInfo = userInfoFuture.get();處拋出了異常

3.2 不帶返回值的異常處理

不帶返回值的異常無法被調(diào)用者捕獲,我們可以實現(xiàn)AsyncUncaughtExceptionHandler來處理異常

創(chuàng)建MyAsyncUncaughtExceptionHandler實現(xiàn)AsyncUncaughtExceptionHandler

@Slf4j
public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {

    @Override
    public void handleUncaughtException(Throwable ex, Method method, Object... params) {
        log.error("ex=",ex);
        // doSomething....
    }
}

創(chuàng)建AsyncConfig實現(xiàn)AsyncConfigurer并覆蓋AsyncUncaughtExceptionHandler方法

@Configuration
@Slf4j
public class AsyncConfig implements AsyncConfigurer {
   
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new MyAsyncUncaughtExceptionHandler();
    }
}

4. 自定義線程池

Spring boot 默認配置的線程池是ThreadPoolTaskExecutor,我們可以通過一些配置來配置線程池

spring.task.execution.pool.core-size=8
spring.task.execution.pool.max-threads=16
spring.task.execution.pool.queue-capacity=100
spring.task.execution.pool.keep-alive=10s

這會將線程池更改為使用有界隊列,最小線程數(shù)為8,最大線程數(shù)為16,隊列最大數(shù)量為100,當線程空閑10秒(而不是默認情況下的60秒)時回收線程時

如果我們想更清楚的控制線程池,也可以配置我們自己的線程池

修改AsyncConfig

@Configuration
@Slf4j
public class AsyncConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(100);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("my-executor-");
        //如果不初始化,導(dǎo)致找到不到執(zhí)行器
        executor.initialize();
        return executor;
    }
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new MyAsyncUncaughtExceptionHandler();
    }
}

這里我們自定義了我們線程池的前綴改成了my-executor-

2018-11-23 16:02:04.797  INFO 15588 --- [nio-8080-exec-2] com.example.async.service.UserService    : 添加用戶成功
2018-11-23 16:02:04.800  INFO 15588 --- [  my-executor-1] com.example.async.service.OuterService   : 獲取用戶詳情成功
2018-11-23 16:02:04.800  INFO 15588 --- [nio-8080-exec-2] com.example.async.service.UserService    : 更新用戶成功
2018-11-23 16:02:04.800  INFO 15588 --- [nio-8080-exec-2] c.e.async.controller.UserController      : 執(zhí)行時間1006
2018-11-23 16:02:14.799  INFO 15588 --- [  my-executor-2] com.example.async.service.MailService    : 發(fā)送郵件成功

5. 多個線程池

有時候我們想讓我們的線程池只做一件事防止多個共用線程池出線搶占溢出情況

我們在AsyncConfig中添加兩個線程池mailExecutor和outerExecutor

@Configuration
@Slf4j
public class AsyncConfig implements AsyncConfigurer {


    @Bean
    public Executor mailExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(100);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("mail-executor-");
        executor.initialize();
        return executor;
    }
    @Bean
    public Executor outerExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(100);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("out-executor-");
        executor.initialize();
        return executor;
    }
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(1);
        executor.setMaxPoolSize(2);
        executor.setQueueCapacity(10);
        executor.setThreadNamePrefix("my-executor-");
        //如果不初始化,導(dǎo)致找到不到執(zhí)行器
        executor.initialize();
        return executor;
    }
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new MyAsyncUncaughtExceptionHandler();
    }
}

然后我們service中分別使用這兩個線程池

MailService使用mailExecutor

@Service
@Slf4j
public class MailService {

    @Async("mailExecutor")
    public void sendMail() throws Exception {
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            log.error(e.getMessage());
        }
        log.info("發(fā)送郵件成功");
    }
}

OuterService使用outerExecutor

@Service
@Slf4j
public class OuterService {

    @Async("outerExecutor")
    public Future<String> getUserInfo() throws Exception {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            log.error("獲取用戶詳情失敗",e);
        }
        log.info("獲取用戶詳情成功");
        return new AsyncResult<>("success");
    }
}

運行效果

2018-11-23 16:18:01.696  INFO 1552 --- [ out-executor-2] com.example.async.service.OuterService   : 獲取用戶詳情成功
2018-11-23 16:18:01.696  INFO 1552 --- [nio-8080-exec-5] com.example.async.service.UserService    : 添加用戶成功
2018-11-23 16:18:01.697  INFO 1552 --- [nio-8080-exec-5] com.example.async.service.UserService    : 更新用戶成功
2018-11-23 16:18:01.697  INFO 1552 --- [nio-8080-exec-5] c.e.async.controller.UserController      : 執(zhí)行時間1001
2018-11-23 16:18:11.699  INFO 1552 --- [mail-executor-2] com.example.async.service.MailService    : 發(fā)送郵件成功

完!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容