【任务篇】Spring Boot 使用 @Async 执行异步任务

写在最前

异步处理不用阻塞当前线程来等待执行完成,而是允许后续操作,直至其它线程将其执行完成,并回调通知此线程。有些业务逻辑可以不用同步处理,并且执行时间长,多数情况都会使用异步处理,直接返回,提升吞吐量。例如:

  • 发送短信、邮件;
  • 卡券或优惠券发放;
  • App消息推送;
  • 工作流性质的异步任务;

Spring Boot 整合异步任务

Demo 地址:mingyue-springboot-async

1.开启异步任务

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@EnableAsync // 开启异步任务
@SpringBootApplication
public class MingYueSpringbootAsyncApplication {

  public static void main(String[] args) {
    SpringApplication.run(MingYueSpringbootAsyncApplication.class, args);
  }
}

2.编写异步任务

import com.csp.mingyue.async.model.MingYueUser;
import java.util.concurrent.Future;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;

/** @author Strive */
@Slf4j
@Component
public class MingYueUserAsyncTask {

  /** 查询用户部门信息 */
  @Async
  @SneakyThrows
  public Future<Boolean> queryDeptInfo(MingYueUser mingYueUser) {
    long start = System.currentTimeMillis();
    Thread.sleep(1000);
    long end = System.currentTimeMillis();
    log.info("{}:查询用户部门信息耗时:({})毫秒", mingYueUser.getUsername(), (end - start));
    return new AsyncResult<>(true);
  }

  /** 查询用户角色信息 */
  @Async
  @SneakyThrows
  public Future<Boolean> queryRoleInfo(MingYueUser mingYueUser) {
    long start = System.currentTimeMillis();
    Thread.sleep(500);
    long end = System.currentTimeMillis();
    log.info("{}:查询用户角色信息耗时:({})毫秒", mingYueUser.getUsername(), (end - start));
    return new AsyncResult<>(true);
  }
}

3.接口测试

import com.csp.mingyue.async.model.MingYueUser;
import com.csp.mingyue.async.service.MingYueUserService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/** @author Strive */
@RestController
@RequiredArgsConstructor
@RequestMapping("/user")
public class MingYueUserController {

  private final MingYueUserService mingYueUserService;

  @GetMapping("/{userId}")
  public ResponseEntity<MingYueUser> queryUserById(@PathVariable Long userId) {
    return ResponseEntity.ok(mingYueUserService.queryUserById(userId));
  }
}

开启异步任务,执行日志如下:

21:37:49.892  INFO 17668 --- [         task-2] c.c.m.async.tasks.MingYueUserAsyncTask   : mingyue:查询用户角色信息耗时:(511)毫秒
21:37:50.393  INFO 17668 --- [         task-1] c.c.m.async.tasks.MingYueUserAsyncTask   : mingyue:查询用户部门信息耗时:(1012)毫秒
21:37:50.394  INFO 17668 --- [nio-8080-exec-1] c.c.m.async.service.MingYueUserService   : 任务全部完成,总耗时:(1026)毫秒

关闭异步任务,执行日志如下:

21:39:38.235  INFO 7032 --- [nio-8080-exec-1] c.c.m.async.tasks.MingYueUserAsyncTask   : mingyue:查询用户部门信息耗时:(1001)毫秒
21:39:38.747  INFO 7032 --- [nio-8080-exec-1] c.c.m.async.tasks.MingYueUserAsyncTask   : mingyue:查询用户角色信息耗时:(510)毫秒
21:39:38.747  INFO 7032 --- [nio-8080-exec-1] c.c.m.async.service.MingYueUserService   : 任务全部完成,总耗时:(1513)毫秒

通过开关闭异步任务,对比结果,高下立判!

  • 异步任务 总耗时:(1026)毫秒;
  • 同步任务 总耗时:(1513)毫秒;

4.配置线程池

@Slf4j
@Configuration
public class MingYueUserInfoThreadPoolConfig {

  @Bean(name = "userInfoThreadPoolExecutor")
  public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
    log.info("---------- 线程池开始加载 ----------");
    ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
    // 核心线程池大小
    threadPoolTaskExecutor.setCorePoolSize(15);
    // 最大线程数
    threadPoolTaskExecutor.setMaxPoolSize(30);
    // 队列容量
    threadPoolTaskExecutor.setQueueCapacity(15);
    // 活跃时间
    threadPoolTaskExecutor.setKeepAliveSeconds(60);
    // 主线程等待子线程执行时间
    threadPoolTaskExecutor.setAwaitTerminationSeconds(60);
    // 线程名字前缀
    threadPoolTaskExecutor.setThreadNamePrefix("user-info-");
    // RejectedExecutionHandler:当pool已经达到max-size的时候,如何处理新任务
    // CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行
    threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    // 初始化
    threadPoolTaskExecutor.initialize();
    log.info("---------- 线程池加载完成 ----------");
    return threadPoolTaskExecutor;
  }
}

执行日志如下:

# pool-executor-2 / pool-executor-1 使用了线程池
22:00:04.180  INFO 16788 --- [pool-executor-2] c.c.m.async.tasks.MingYueUserAsyncTask   : mingyue:查询用户角色信息耗时:(512)毫秒
22:00:04.679  INFO 16788 --- [pool-executor-1] c.c.m.async.tasks.MingYueUserAsyncTask   : mingyue:查询用户部门信息耗时:(1011)毫秒
22:00:04.679  INFO 16788 --- [nio-8080-exec-1] c.c.m.async.service.MingYueUserService   : 任务全部完成,总耗时:(1020)毫秒
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,下面是一个简单的示例: 假设我们有一个 UserService,其中有一个方法 sendEmail,它需要异步地发送电子邮件。现在我们想要将当前用户的信息透传给异步任务使用线程。 首先,我们需要在异步方法上添加 @Async 注解,并在配置类中启用异步支持: ```java @Configuration @EnableAsync public class AppConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); executor.setMaxPoolSize(100); executor.setQueueCapacity(10); executor.initialize(); return executor; } @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return new CustomAsyncExceptionHandler(); } } ``` 在上面的示例中,我们创建了一个 ThreadPoolTaskExecutor,它将用于执行异步任务。我们还实现了 AsyncConfigurer 接口,并覆盖了 getAsyncExecutor 和 getAsyncUncaughtExceptionHandler 方法,以提供自定义的 Executor 和异常处理程序。 现在我们需要将当前用户信息存储在一个 ThreadLocal 对象中。这可以通过一个拦截器来实现: ```java public class UserContextInterceptor extends HandlerInterceptorAdapter { private final ThreadLocal<String> userThreadLocal = new ThreadLocal<>(); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String currentUser = request.getHeader("X-User"); userThreadLocal.set(currentUser); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { userThreadLocal.remove(); } public String getCurrentUser() { return userThreadLocal.get(); } } ``` 在上面的示例中,我们创建了一个 UserContextInterceptor,它将在每个请求的开始和结束时执行。在 preHandle 方法中,我们从请求头中获取当前用户信息,并将其存储在一个 ThreadLocal 对象中。在 afterCompletion 方法中,我们将删除该信息,以避免内存泄漏。 现在,我们可以在 UserService 的 sendEmail 方法中使用 UserContextInterceptor 中存储的当前用户信息: ```java @Service public class UserService { @Autowired private JavaMailSender mailSender; @Autowired private UserContextInterceptor userContextInterceptor; @Async public void sendEmail(String to, String subject, String text) { String currentUser = userContextInterceptor.getCurrentUser(); // 使用当前用户信息发送电子邮件 // ... } } ``` 在上面的示例中,我们使用 @Autowired 注解将 UserContextInterceptor 注入到 UserService 中。在 sendEmail 方法中,我们从 UserContextInterceptor 中获取当前用户信息,并在发送电子邮件时使用它。 通过这种方式,我们可以将当前用户信息透传给异步任务使用线程
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Strive_MY

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值