Spring Boot多线程使用


在之前的两篇文章中分别介绍了线程的使用与线程锁的应用,还没看过的小伙伴可以点击直通:Java多线程编程Java线程锁介绍

这篇文章将介绍一下如何在 Spring Boot 项目中使用线程池。 Spring Boot 中一共有两种线程调用方式,一类为通过手动提交线程任务的方式,另一类则通过注解的方式,下面将针对两类方式分别进行说明。

一、执行策略

Spring 提供了 ThreadPoolTaskExecutor 用于创建线程池,下面将具体介绍使用方式。

1. 执行流程

当在代码中向线程池提交一个线程任务时,系统将会根据下述流程决定线程的创建销毁与否。

(Ⅰ) 判断线程池中当前存活线程数量是否达到了 corePoolSize

  • 达到 :进入下一步。
  • 未达到 :新建线程运行此任务,且任务结束后将该线程保留在线程池中,不做销毁处理。

(Ⅱ) 判断工作队列 (queueCapacity) 是否已满。

  • 已满 :进入下一步。
  • 未满 :将新的任务提交到工作队列中进入等待。

(Ⅲ) 判断线程池中存活的线程数量是否达到了 maxPoolSize

  • 达到 :使用 饱和策略 来处理这个任务。
  • 未达到 :新建一个工作线程来执行这个任务。

需要注意一点:在线程池中的线程数量超过 corePoolSize 时,每当有线程的空闲时间超过了 keepAliveTime ,这个线程就会被终止,直到线程池中线程的数量不大于 corePoolSize 为止。

2. 饱和策略

通过饱和策略用于管理当线程资源已满时如何处理新提交的线程任务,默认使用 CallerRunsPolicy

策略 描述
AbortPolicy 中止策略,默认的饱和策略,该策略将抛出未检查的 RejectedExecutionException。
DiscardPolicy 抛弃策略,会悄悄抛弃该任务,当新提交的任务无法保存到队列中等待执行时。
DiscardOldestPolicy 抛弃最旧的策略,会抛弃下一个将被执行的任务,然后尝试重新提交新的任务。
CallerRunsPolicy 该策略既不会抛弃任务,也不会抛出异常,而是由调用线程(提交任务的线程)处理该任务。

二、配置介绍

1. 配置方式

在项目中新建 ThreadConfig 类,用于向容器中注入相应 bean 对象:

@Configuration
public class ThreadConfig {

    @Bean
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数(默认线程数)
        executor.setCorePoolSize(5);
        // 最大线程数
        executor.setMaxPoolSize(20);
        // 缓冲队列大小
        executor.setQueueCapacity(10000);
        // 允许线程空闲时间(单位:默认为秒)
        executor.setKeepAliveSeconds(30);
        // 线程池名前缀
        executor.setThreadNamePrefix("ibudai-");
        // 线程池对拒绝任务的处理策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 初始化
        executor.initialize();
        return executor;
    }
}

2. 调用线程池

完成 bean 对象注入后即可通过 @Autowired 获取实例即可,其同样提供 execute()submit() 两种提交方式,区别在于前者没有返回值。

public class ThreadTest {

    @Autowired
    private ThreadPoolTaskExecutor taskExecutor;

    public void demo() {
        taskExecutor.execute(() -> {
            System.out.println("提交一个异步线程");
            try {
                // 模拟耗时
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
    }
}

三、注解方法

Spring Boot 中除了上述显示的通过 taskExecutor 线程池方式提交任务还可以通过 @Async 方式往线程池提交任务。

1. 启动异步

通过注解方法使用异步任务前需要在项目启动类上添加 @EnableAsync 开启异步。

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

2. 异步方法

在需要异步处理的方法上添加 @Async 即可,当主线程调用该注解标注的方法相当于创建一个线程执行该任务。

如果项目中配置了多个线程池可以通过 @Async("pool-name") 指定线程池资源。

public class MyTest {

    public static void main(String[] args) {
        this.Task();
        System.out.println("info...");
    }

    @Async
    public void Task() {
        for (int i = 0; i < 5; i++) {
            System.out.println("正在执行任务 " + i);
        }
        System.out.println("任务执行完成");
    }
}

当在 main 方法中调用使用 @Async 标注的方法时,等价于创建一个线程用于执行异步任务,等价于如下代码:

public static void main(String[] args) {
    new Thread(() -> {
        for (int i = 0; i < 5; i++) {
            System.out.println("正在执行任务 " + i);
        }
        System.out.println("任务执行完成");
    }).start();
    System.out.println("info...");
}

文章作者: 烽火戏诸诸诸侯
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 烽火戏诸诸诸侯 !
  目录