我悟了!难怪阿里禁止使用Executors 创建线程池

阿里的《Java 开发手册》有下面这一条编程规约。
在这里插入图片描述
一开始我是百思不得其解,直到我了解了Executors线程池.

要想深刻的理解这一条,我们需要了解线程池的几个参数。我们看下ThreadPoolExecutor构造函数所需要的参数。

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler{
    //省略实现
}

参数的含义如下

  • corePoolSize => 核心线程数量
  • maximunPoolSize => 最大线程数量
  • keepAliveTime => 允许线程空闲的时间。空闲时间超过该数值会进行相应的处理
  • unit => keepAliveTime的基本单位,常用的有毫秒和秒
  • workQueue 线程池的工作队列,属于阻塞队列
  • threadFactory 线程工厂,用于创建线程
  • handler 线程池已满对新来任务采取的拒绝策略,也称饱和策略

当有一个新的任务来到线程池,线程池当前线程数量为poolSize

  1. 如果poolSize<corePoolSize,则创建新的线程来执行任务
  2. 如果poolSize>=corePoolSize,但是workQueue工作队列还没有满,则任务进入到工作队列中。
  3. 如果工作队列已满,但是poolSize<maxmumPoolSize,则创建新的线程来执行任务
  4. 如果poolSize>=maxmumPoolSize,则对于新来的任务根据指定的饱和策略进行处理

饱和策略主要有以下几种

  1. AbortPolicy => 默认策略,直接抛出RejectedExecutionException异常
  2. DiscardPolicy => 放弃最新提交的任务
  3. DiscardOldestPolicy => 放弃接下来要执行的任务
  4. CallerRuns => 任务被拒绝添加后,会用调用execute函数的上层线程去执行被拒绝的任务

有了基本的认识后,我们再看看Executors提供的几种线程池,就能够明白为什么阿里会强制禁止使用Executors创建线程池了

一、FixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
}

很明显,FixedThreadPool的corePoolSize=maximunPoolSize ,且具体数值是由我们指定,也难怪叫做固定线程池,因为核心线程数等于固定线程数,而核心线程不会默认不会被回收处理,因此线程数量是固定的,接下来看看对于的工作队列LinkedBlockingQueue源码中如何设置容量

    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }

    public LinkedBlockingQueue(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        last = head = new Node<E>(null);
    }

该队列中可以指定容量,但是默认为int的最大值2^31-1。看到这里我们就明白了,我们重新思考阿里编程规约的话,因为工作队列的容量太大,当线程数量大于核心线程数且使用默认工作队列长度时,任务会大量堆积到工作队列,很容易导致OOM。

二、SingleTheadPool

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

同样的,我们观察到corePoolSize=maximunPoolSize=1,单一线程池的线程数量固定,为一,也因此称为单一线程池。工作队列同样使用LinkedBlockingQueue,因此也可能堆积大量任务导致OOM

三、CachedThreadPool

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

在缓存线程池中,corePoolSize=0,maximumPoolSize=2^31-1,我们看下SynchronousQueue这个工作队列,这个队列比较特殊,该工作队列不保存任何任务被等待执行,而是直接提交给线程进行执行,容量可以理解为0

因此,如果创建的任务过多,会一直创建线程去执行任务,可能会导致OOM

四、ScheduledThreadPool

    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

分析过程很熟悉了,corePoolSize我们可以自己指定,同样maximumPoolSize=2^31-1,但是这个延迟工作队列DelayedWorkQueue我们没有看过,点进去他的源码

        private static final int INITIAL_CAPACITY = 16;
        
        private void grow() {
            int oldCapacity = queue.length;
            int newCapacity = oldCapacity + (oldCapacity >> 1); // grow 50%
            if (newCapacity < 0) // overflow
                newCapacity = Integer.MAX_VALUE;
            queue = Arrays.copyOf(queue, newCapacity);
        }

我又懂了,这个队列初始容量为16,容量满了后会进行扩容,每次扩容50%,最大值同样为int最大值,而且还是一个延迟队列。

五、总结

  • 线程池工作原理是当线程数量小于核心线程数,则创建线程执行任务。当线程数量大于核心线程数,则任务进入工作队列。当工作队列已满,则创建线程执行任务,直到线程数量等于最大线程数
  • FixedThreadPool和SingleTheadPool使用了LinkedBlockingQueue这个无界队列,可能会导致大量请求或任务堆放在队列中导致OOM
  • CachedThreadPool和ScheduledThreadPool本身最大线程数maxmumPoolSize是int的最大值,可能创建过多的线程导致oom
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值