前言:

        SpringBoot 的异步多线程需要从 java 的多线程基础说起,可以参考 java 多线程实现的三种方式区别。SpringBoot 在此基础上进行了多次封装,所以使用起来非常方便。

一、核心参数说明

        ThreadPoolExecutor 是 java 的核心线程池类;Spring 对 ThreadPoolExecutor 进行二次封装形成了 ThreadPoolTaskExecutor,其中几个核心参数除了名字略有改动,核心含义没变,下面说明一下:

  • corePoolSize:核心线程池数
  • maxPoolSize:最大线程池数
  • queueCapacity:线程池队列最大容量
  • keepAliveSeconds:允许线程的空闲时间,核心线程外的线程在空闲时间到达后会被销毁
  • threadNamePrefix:线程池名的前缀
  • rejectedExecutionHandler:拒绝策略

其中拒绝策略是线程达到某种饱和后的线程池的操作策略,总共四种:

  • AbortPolicy:如果线程池队列满了丢掉任务并且抛出RejectedExecutionException异常
  • DiscardPolicy:如果线程池队列满了,会直接丢掉这个任务并且不会抛出异常
  • DiscardOldestPolicy:如果队列满了,会将最早进入队列的任务删掉,再尝试加入队列
  • CallerRunsPolicy:如果添加到线程池失败,那么主线程会自己去执行该任务

说明一下几个核心参数和拒绝策略是怎么工作的:

1.核心线程数:m,最大线程数: n(一般 n > m),线程池队列最大容量: j,线程总数: s;

2.线程进场后(s<m),线程会直接启动直到启动的线程数达到核心线程数(s=m);

3.线程继续进场(m<s<m+j),线程开始排队,此时启动的线程数不会增加直到队列饱和(s=m+j);

4.线程继续进场(m+j<s<n+j),每进场一个线程,该线程就会启动;直到启动的线程达到最大线程数(s=n+j);

5.线程继续进场(s>n+j),此时触发拒绝策略;

二、使用说明

  首先配置线程池:

@Configuration
@EnableAsync
public class ThreadPoolConfig {

    @Bean("normalThreadPool")  //线程池实例名,多个线程池配置需要声明,一个线程池可有可无
    public Executor executorNormal() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(3);
        executor.setMaxPoolSize(5);
        executor.setQueueCapacity(3);
        executor.setKeepAliveSeconds(60);
        executor.setThreadNamePrefix("NORMAL--");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        executor.initialize();
        return executor;
    }
}

其次在需要异步的方法上加 @Async 注解:

@Slf4j
@Service
public class ThreadTaskService {

    @Async("normalThreadPool") //多个线程池配置时需指定配置实例
    public void task() {
        log.info("task start...");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("task end...");
    }

}

异步的调用跟普通 service 方法没区别:

@Slf4j
@RestController
@RequestMapping("/thread")
public class ThreadTaskController {

    @Autowired
    ThreadTaskService taskService;

    @GetMapping(value = "/start")
    public String getValue() {
        taskService.task();
        return "hello...";
    }
}

三、带返回值的异步

要获取异步函数的返回值可以使用 Future,但是Future 的get方法是阻塞的,使用时需要注意。

    @Async("normalThreadPool")
    public CompletableFuture<String> task() {
        String result = "000";
        log.info("task start...");
        try {
            Thread.sleep(5000);
            result = "333";
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("task end...");
        return CompletableFuture.completedFuture(result);
    }
@Slf4j
@RestController
@RequestMapping("/thread")
public class ThreadTaskController {
    @Autowired
    ThreadTaskService taskService;

    @GetMapping(value = "/start")
    public String getValue() throws ExecutionException, InterruptedException {

        CompletableFuture<String> result = taskService.task();

        log.info("result:{}", result.get()); // get 方法会使主线程阻塞

        return "hello...";
    }
}

四、几种异步失败的情况

1. 异步方法使用static关键词修饰;

2. 缺少 @EnableAsync 注解;

3. 同一个类中,一个方法调用另外一个有@Async注解的方法(原因是@Async注解的方法,是在代理类中执行的);

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐