为什么阿里巴巴Java开发手册中,强制我们不允许用Executors去创建线程池?

本文主要介绍:

  1. 什么是线程池?
  2. 如何理解线程池的构造方法
  3. 使用Executors创建线程池有什么问题?
  4. 正确创建线程池的姿势

  在阿里巴巴Java开发手册中,有一条强制规则,不允许使用Executors去创建线程池,为什么会有这条规则呢?带着这个问题,今天我们来一起探究一下Java线程池的工作原理
为什么阿里巴巴Java开发手册中,强制我们不允许用Executors去创建线程池?_第1张图片


  首先抛出一个问题,什么是线程池?
  从线程池的定义上来讲,线程池(Thread Pool)是一种基于池化思想的线程管理工具,过多的线程会带来额外的开销,比如创建或销毁线程、线程调度等。线程池通过维护多个线程,来避免这种开销,同时也在一定程度上避免了线程数量的膨胀。
  从使用上来讲,线程池实现的是线程的复用,每提交一个任务,这个任务会被分配给一个线程去执行,任务执行完,会将线程归还至线程池

  如何理解线程池的构造方法?
  我们可以来看一下用 new TheadPoolExecutor 方法创建线程池的参数

public ThreadPoolExecutor(int corePoolSize,                           // 核心线程数
                              int maximumPoolSize,                    // 最大线程数
                              long keepAliveTime,                     // Keep Alive 时间
                              TimeUnit unit,                          // 时间单位
                              BlockingQueue workQueue,      // 工作队列
                              ThreadFactory threadFactory,            // 线程工厂
                              RejectedExecutionHandler handler)       // 拒绝策略

为什么阿里巴巴Java开发手册中,强制我们不允许用Executors去创建线程池?_第2张图片
  图片摘自《Java线程池实现原理及其在美团业务中的实践》,强烈建议大家去读一下原文。

  这里我们先入为主的给出线程池执行任务的步骤,大家可以结合上的图示,对照一下。线程池的设计,符合生产消费模型

  1. 线程池初始化后,不会立刻创建线程,而是等到第一个任务提交后,才创建线程
  2. 线程创建的个数如果大于CorePoolSize,任务就会被放到工作队列中暂存起来
  3. 工作队列存满后,创建线程至maximumPoolSize个线程
  4. 线程数已经创建至maximumPoolSize个线程,仍没有空闲线程消费任务,新提交上来的任务会根据RejectedExecutionHandler(拒绝策略),进行拒绝操作
  5. 当前线程数大于核心线程数时,线程等待KeepAliveTime后,仍没有任务提交上来,则认为线程空闲,线程池会收缩线程至corePoolSize

  有时间的同学可以阅读一下线程池的源码,对照着上面的步骤,看一下代码层面上是如何实现的

  所有的拒绝策略都实现了RejectedExecutionHandler 这个接口
  AbortPolicy: 直接抛出 RejectedExecutionException
  CallerRunsPolicy: 由提交任务的线程去执行
  DiscardOldestPolicy: 丢弃工作队列中等待时间最长的任务, 后将任务提交至工作队列
  DiscardPolicy: 什么都不做, 直接将任务丢弃


  那么回到问题本身,使用Executors创建线程池有什么问题?
  FixedThreadPool / SingleThreadPool 的工作队列是无界的,大量任务堆积,容易产生OOM
  CacheThreadPool / ScheduledThreadPool 可以创建的线程的个数是无界的,创建过多的线程会导致OOM或者是 cpuload飙升

  通常我们可能存在侥幸心理,认为这种情况基本不会发生,但是在高并发的场景下,这种情况却很容易出现,比如我们依赖下游服务,QPS为100,假设我们客户端的超时时间为1分钟,由于网络抖动,下游服务有1min的时间不可用,这个时间我们的请求会被hang住,1min的时间会产生 6000个请求,如果是CacheThreadPool异步调用,我们会在1min之内创建6000个线程,很容易使服务OOM


正确创建线程池的姿势
  这里抛砖引玉,给出一个通过guava ThreadFactory创建线程池的demo,仅做参考

@Slf4j
class Solution {
    private static final int CORE_POOL_SIZE = 10;
    private static final int MAX_POOL_SIZE = 150;
    private static final int BLOCKING_QUEUE_SIZE = 10;
    private static final long KEEP_ALIVE_TIME = 10L;

    private static final ThreadFactory guavaThreadFactory =
            new ThreadFactoryBuilder().setNameFormat("thread-pool-%d").build();
    
    private static final ExecutorService exec = new ThreadPoolExecutor(CORE_POOL_SIZE,
            MAX_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(BLOCKING_QUEUE_SIZE), guavaThreadFactory);


    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            exec.submit(() -> log.info("is working"));
        }
    }
}

  创建线程池的时候,需要做到核心功能和非核心功能分开始用,配置不同的参数,避免相互影响
  感谢阅读、欢迎交流

你可能感兴趣的:(java后端)