vlambda博客
学习文章列表

项目中使用线程池的心得总结

1 线程池在项目中的运用总结

1.1 背景

工作中,用到线程池的地方是同步人员工作量的功能,该功能是选择时间同步人员工作量,工作量来自禅道系统+Digiprocess 系统。

正常是发起工作量请求,业务逻辑处理,响应入库。这样存在一个很大的弊端就是多了一次请求别的系统的数据+数据库操作,进而可能会导致响应速度较慢。

使用线程池,将同步方法放入队列中,然后直接返回响应,会极大的提高接口的响应速度。

1.2 线程池的作用

  • 线程复用
  • 线程资源管理
  • 控制操作系统的最大并发数、以保证系统高效且安全的运行

1.3 线程池核心参数

  • corePoolSize 线程池中核心的线程数量
  • maxmumPoolSize 线程池最大线程池的数量
  • keepAliveTime 当线程超过 corePoolSize,多余的空闲线程的存活时间
  • unit KeepAliveTime 的单位
  • workQueue 任务队列
  • threadFactory 线程工厂
  • handler 拒绝策略,队列满了,并且工作线程大于等于线程池的最大线程数

1.4 工作原理

1.5 三种重要的线程池

  • newFixedThreadPool 固定大小线程池,执行长期的任务
  • newSingleThreadExecutor 单个线程的线程池
  • newCachedTreadPool 可缓存的线程池 适用于短期任务

1.6 JDK 内置的拒绝策略

  • AbortPolicy 直接抛出rejectedExecutionException 异常阻止系统正常运行
  • CallerRunsPolicy 不会丢弃任务,将某些任务回调到调用者
  • DiscarOldPolicy 抛弃队列中等待最久的任务,然后把当前任务加入队列
  • DiscarPolicy 直接丢弃任务

2 SpringBoot线程池的创建

2.1 启动类配置

在SpringBoot的主程序中配置@EnableAsync

2.2 配置

@EnableAsync
@Configuration
public class TaskPoolConfig {
    /** 
 *   默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,
 *    当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
 *  当队列满了,就继续创建线程,当线程数量大于等于maxPoolSize后,开始使用拒绝策略拒绝 
 */
   @Value("${task.pool.corePoolSize}")
   private Integer corePoolSize;//设置核心线程数
   @Value("${task.pool.maxPoolSize}")
   private Integer maxPoolSize;//设置最大线程数
   @Value("${task.pool.keepAliveSeconds}")
   private Integer keepAliveSeconds;//设置线程活跃时间(秒)
   @Value("${task.pool.queueCapacity}")
   private Integer queueCapacity;//设置队列容量
   
   @Bean("taskExecutor")
   public Executor taskExecutor() {
      //创建一个线程池
      ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
      // 核心线程数10:线程池创建时候初始化的线程数
      executor.setCorePoolSize(corePoolSize);
      // 最大线程数20:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
      executor.setMaxPoolSize(maxPoolSize);
      // 缓冲队列200:用来缓冲执行任务的队列
      executor.setQueueCapacity(queueCapacity);
      // 允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
      executor.setKeepAliveSeconds(keepAliveSeconds);
      // 线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
      executor.setThreadNamePrefix("taskExecutor-");
      // 线程池对拒绝任务的处理策略:
      // 这里采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,
      // 该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;
      // 如果执行程序已关闭,则会丢弃该任务
      executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
      return executor;
   }
}
@Async会默认从线程池获取线程,当然也可以显式的指定@Async("asyncTaskExecutor")

2.3 properties配置

### 线程池
task.pool.corePoolSize=10
task.pool.maxPoolSize=20
task.pool.keepAliveSeconds=60
task.pool.queueCapacity=200

2.4 注意事项

如下方式会使@Async失效

  • 异步方法使用static修饰
  • 异步类没有使用@Component注解(或其他注解)导致spring无法扫描到异步类
  • 异步方法不能与被调用的异步方法在同一个类中
  • 类中需要使用@Autowired或@Resource等注解自动注入,不能自己手动new对象
  • 如果使用SpringBoot框架必须在启动类中增加@EnableAsync注解