自定义线程池的使用

245 阅读2分钟

本文已参与「新人创作礼」活动, 一起开启掘金创作之路。

今天说一下线程池的使用和一些参数的配置

在jdk中有一个Executors框架提供了各种类型的线程池

  • public static ExecutorService newCachedThreadPool() 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。
  • public static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程,超出的线程会在队列中等待。
  • public static ExecutorService newSingleThreadExecutor() 创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
  • public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 创建一个定长线程池,支持定时及周期性任务执行。

然而阿里开发手册上并不推荐使用jdk自带的线程池框架来创建线程池

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.acc = System.getSecurityManager() == null ?
            null :
            AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

经过查看源码,Executors框架在创建线程池时使用的是LinkedBlockingQueue()。也就是说通过 new LinkedBlockingQueue()创建的队列长度是Interger.MAX_VALUE。任务队列的长度并没有限制,导致任务大量排队不执行拒绝策略,导致队列中任务堆积过多导致OOM。

那么这时候我们就需要手动创建线程池

手动创建线程池有什么好处呢,使我们更加明确线程池的运行规则,更加灵活可控制线程池参数,规避资源耗尽的风险。 下面我们看一下如何创建一个优秀的线程池

@Bean
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
    ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
    // 核心线程数
    threadPoolTaskExecutor.setCorePoolSize(corePoolSize);
    // 最大线程数
    threadPoolTaskExecutor.setMaxPoolSize(maxPoolSize);
    // 工作队列容量
    threadPoolTaskExecutor.setQueueCapacity(queueCapacity);
    // 拒绝策略:任务添加失败由调用线程执行
    threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    // 线程的名字前缀
    threadPoolTaskExecutor.setThreadNamePrefix("thread-order-");
    return threadPoolTaskExecutor;
}

线程池的七个核心参数

  • corePoolSize:核心线程数
  • maxPoolSize:最大线程数
  • queueCapacity:工作队列容量
  • keepAliveTime:空闲线程存活时间
  • unit:空闲线程存活时间单位
  • workQueue:工作队列
  • handler:拒绝策略

在实际的日常工作中,线程的核心线程数及最大线程数都是根据业务需要来设置的。一般最大线程数是核心线程的2倍。