SpringBoot中线程池的使用

一、快速入门

1、异步方法

在SpringBoot中,要实现一个异步执行的方法很简单,只需在项目中添加@EnableAsync注解(一般添加在启动类或线程池配置类上),并在需要异步执行的方法上添加@Async注解即可。

在启动类上添加@EnableAsync注解:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@EnableAsync
@EnableScheduling
@EnableTransactionManagement
@SpringBootApplication
public class RedisApplication {

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

}

创建一个Service接口:

public interface AsyncService {
    void simpleAsync();
}

 实现该接口及其方法,并在方法上添加@Async异步注解:

import com.redis.service.AsyncService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class AsyncServiceImpl implements AsyncService {
    @Override
    @Async
    public void simpleAsync() {
        log.info("ThreadName【{}】异步方法开始……", Thread.currentThread().getName());
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("ThreadName【{}】异步方法结束。", Thread.currentThread().getName());
    }
}

因此实现了一个异步执行的方法,编写一个单元测试进行测试: 

@GetMapping("/selectAll")
    public HashMap<String, Student> selectAll(){
        List<Student> students = studentMapper.selectList(null);
        HashMap<String,Student> map=new HashMap<>();
        for (Student student : students) {
            map.put(student.getSId(),student);
        }

        asyncService.simpleAsync();;
        asyncService.simpleAsync();
        // 等待一段时间让异步方法执行结束
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return map;
    }

运行单元测试,即可见到两个线程名称不同的异步方法交替执行:

2、自定义线程池 

上面的异步方法是通过SpringBoot中自动注入的线程池任务执行器实现的,我们并未创建任何线程池。大部分情况下自动注入的线程池不符合实际需求,需要根据实际场景自定义线程池。

在项目config目录下创建一个线程池配置类ThreadPoolConfig,编写一个方法创建并初始化一个线程池任务执行器对象:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

@Configuration
public class ThreadPoolConfig {
    @Bean(name = "threadPool_1")
    public ThreadPoolTaskExecutor threadPool01() {
        // 创建线程池任务执行器对象
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 设置核心线程数量
        executor.setCorePoolSize(8);
        // 设置最大线程数量
        executor.setMaxPoolSize(16);
        // 设置阻塞队列容量
        executor.setQueueCapacity(256);
        // 设置线程空闲时间,默认为 60 秒
        executor.setKeepAliveSeconds(60);
        // 设置是否支持回收核心线程,默认为 false
        executor.setAllowCoreThreadTimeOut(false);
        // 设置线程名称前缀,若不设置则根据对象的 beanName 生成
        executor.setThreadNamePrefix("threadPool_1_");
        // 设置线程池拒绝策略,默认为 AbortPolicy,即线程数量达到最大线程数量,且阻塞队列容量已满,再添加任务则抛出异常。
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        // 初始化
        executor.initialize();
        return executor;
    }
}

 在@Async注解中指定线程池任务执行器名称,即可使用该线程池执行异步任务:

import com.redis.service.AsyncService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class AsyncServiceImpl implements AsyncService {
    @Override
    @Async(value = "threadPool_1")
    public void simpleAsync() {
        log.info("ThreadName【{}】异步方法开始……", Thread.currentThread().getName());
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("ThreadName【{}】异步方法结束。", Thread.currentThread().getName());
    }
}

指定线程池后重新执行单元测试,发现线程名称前缀为配置中设置的值,说明使用了自定义的线程池执行异步任务: 

3、注意事项

  • @Async注解是通过切面代理的方式异步执行该注解修饰的方法,因此只有注入Bean对象直接调用才能实现异步,在类的内部调用是无法实现异步的
  • SpringBoot只有在未注入任何执行器对象的时候才会自动注入线程池任务执行器对象,只要手动注入任意一个线程池任务执行器对象,SpringBoot便不会再自动注入。
  • 手动注入多个线程池任务执行器对象时,若@Async注解不指定名称,则会任意选取一个线程池任务执行器对象执行异步任务,所以在添加@Async注解时最好指定名称,或在一个线程池任务执行器对象注入时添加@Primary注解,那所有未指定名称的异步任务就都会通过该线程池执行。
  • import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Primary;
    import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
    
    import java.util.concurrent.ThreadPoolExecutor;
    
    @Configuration
    public class ThreadPoolConfig {
        @Primary
        @Bean(name = "threadPool_1")
        public ThreadPoolTaskExecutor threadPool01() {
            // 创建线程池任务执行器对象
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            // 设置核心线程数量
            executor.setCorePoolSize(8);
            // 设置最大线程数量
            executor.setMaxPoolSize(16);
            // 设置阻塞队列容量
            executor.setQueueCapacity(256);
            // 设置线程空闲时间,默认为 60 秒
            executor.setKeepAliveSeconds(60);
            // 设置是否支持回收核心线程,默认为 false
            executor.setAllowCoreThreadTimeOut(false);
            // 设置线程名称前缀,若不设置则根据对象的 beanName 生成
            executor.setThreadNamePrefix("threadPool_1_");
            // 设置线程池拒绝策略,默认为 AbortPolicy,即线程数量达到最大线程数量,且阻塞队列容量已满,再添加任务则抛出异常。
            executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
            // 初始化
            executor.initialize();
            return executor;
        }
    }

二、ThreadPoolTaskExecutor线程池参数 

自定义线程池时可以根据不同的业务场景配置不同的线程池参数,以达到不同的效果。

1、核心线程数量

线程池中固定存在的线程数量,如果当前线程池中正在运行的线程数量少于核心线程数量,再添加一个异步任务时,会马上有一个线程执行该任务。ThreadPoolTaskExecutor默认核心线程数量为1
ThreadPoolTaskExecutor设置核心线程数量:

public void setCorePoolSize(int corePoolSize) {
    synchronized(this.poolSizeMonitor) {
        this.corePoolSize = corePoolSize;
        if (this.threadPoolExecutor != null) {
            this.threadPoolExecutor.setCorePoolSize(corePoolSize);
        }
    }
}

ThreadPoolTaskExecutor获取核心线程数量:

public int getCorePoolSize() {
    synchronized(this.poolSizeMonitor) {
        return this.corePoolSize;
    }
}

2、队列容量

当线程池中活跃的线程数量达到核心线程数量后,继续添加异步任务时,新增的任务会进入阻塞队列中,队列容量即阻塞队列中能容纳的任务数量。ThreadPoolTaskExecutor默认队列容量为2147483647(int最大值)
ThreadPoolTaskExecutor设置队列容量:

public void setQueueCapacity(int queueCapacity) {
    this.queueCapacity = queueCapacity;
}

通过ThreadPoolTaskExecutor中的createQueue可以知道,当传入的queueCapacity大于0时,阻塞队列为指定容量的LinkedBlockingQueue对象,否则为SynchronousQueue对象。

protected BlockingQueue<Runnable> createQueue(int queueCapacity) {
    return (BlockingQueue)(queueCapacity > 0 ? new LinkedBlockingQueue(queueCapacity) : new SynchronousQueue());
}

 3、最大线程数量

当线程池中活跃的线程数量达到核心线程数量且阻塞队列容量满了之后,继续添加异步任务时,若活跃线程数量还未达到最大线程数量,则会创建新的线程执行队列中的异步任务。ThreadPoolTaskExecutor默认最大线程数量为2147483647(int最大值)
ThreadPoolTaskExecutor设置最大线程数量:

public void setMaxPoolSize(int maxPoolSize) {
    synchronized(this.poolSizeMonitor) {
        this.maxPoolSize = maxPoolSize;
        if (this.threadPoolExecutor != null) {
            this.threadPoolExecutor.setMaximumPoolSize(maxPoolSize);
        }
    }
}

ThreadPoolTaskExecutor获取最大线程数量:

public int getMaxPoolSize() {
   synchronized(this.poolSizeMonitor) {
       return this.maxPoolSize;
   }
}

4、线程空闲时间

核心线程外的线程空闲时存活的时间,默认存活时间为60秒
ThreadPoolTaskExecutor设置线程空闲时间:

public void setKeepAliveSeconds(int keepAliveSeconds) {
    synchronized(this.poolSizeMonitor) {
        this.keepAliveSeconds = keepAliveSeconds;
        if (this.threadPoolExecutor != null) {
            this.threadPoolExecutor.setKeepAliveTime((long)keepAliveSeconds, TimeUnit.SECONDS);
        }
    }
}

ThreadPoolTaskExecutor获取线程空闲时间:

public int getKeepAliveSeconds() {
    synchronized(this.poolSizeMonitor) {
        return this.keepAliveSeconds;
    }
}

5、是否允许销毁核心线程

ThreadPoolTaskExecutor默认不允许销毁核心线程,即核心线程空闲时间达到设置值时,也不会被销毁。可通过以下方法设置:

public void setAllowCoreThreadTimeOut(boolean allowCoreThreadTimeOut) {
    this.allowCoreThreadTimeOut = allowCoreThreadTimeOut;
}

6、线程名称前缀

public void setThreadNamePrefix(@Nullable String threadNamePrefix) {
    super.setThreadNamePrefix(threadNamePrefix);
    this.threadNamePrefixSet = true;
}

7、拒绝策略

当线程池中异步任务数量达到阻塞队列容量且活跃线程数量达到最大线程数量时,继续添加异步任务则会触发拒绝策略。
四种拒绝策略:

  • AbortPloicy(默认):抛出异常
  • CallerRunsPolicy:由调用方执行任务
  • DiscardPolicy:丢弃超出的任务
  • DiscardOldestPolicy:丢弃阻塞队列中最早的任务

ThreadPoolTaskExecutor设置拒绝策略:

public void setRejectedExecutionHandler(@Nullable RejectedExecutionHandler rejectedExecutionHandler) {
    this.rejectedExecutionHandler = (RejectedExecutionHandler)(rejectedExecutionHandler != null ? rejectedExecutionHandler : new AbortPolicy());
}

猜你喜欢

转载自blog.csdn.net/weixin_55127182/article/details/132405990