阿里巴巴:禁止使用 Executors 创建线程池!为什么啊?

简介: 阿里巴巴:禁止使用 Executors 创建线程池!为什么啊?

Executor是不建议的


Executors类为我们提供了各种类型的线程池,经常使用的工厂方法有:


public static ExecutorService newSingleThreadExecutor()
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newCachedThreadPool()
public static ScheduledExecutorService newSingleThreadScheduledExecutor()
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)


书写一段很简单的测试代码:


public class ThreadDemo {
 public static void main(String[] args) {
  ExecutorService es = Executors.newSingleThreadExecutor();
 }
}


当我们用阿里巴巴的P3C检查代码时,会被教育的!!!!


1.png


阿里爸爸是不允许这么创建线程池的,上面的警告写的很明确“线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。”(PS:很难得在编译器中看到中文提示,对于英语不好的同学来说,简直是福音,喜极而泣!!!!)


强制使用ThreadPoolExecutor


我们使用ThreadPoolExecutor创建线程池:


public class ThreadDemo {
 public static void main(String[] args) {
  ExecutorService es = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue<Runnable>(10), Executors.defaultThreadFactory(),
    new ThreadPoolExecutor.DiscardPolicy());
 }
}


此时,再用P3C检查代码,终于没有报错了。


在华丽的分隔符之后,我们还是有必要从JDK源码的层面深挖一下其中的原理。


首先是静态方法newSingleThreadExecutor()、newFixedThreadPool(int nThreads)、newCachedThreadPool()。我们来看一下其源码实现(基于JDK8)。


public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
}


通过查看源码我们知道上述三种静态方法的内部实现均使用了ThreadPoolExecutor类。难怪阿里爸爸会建议通过ThreadPoolExecutor的方式实现,原来Executors类的静态方法也是用的它,只不过帮我们配了一些参数而已。


第二是ThreadPoolExecutor类的构造方法。既然现在要直接使用ThreadPoolExecutor类了,那么其中的初始化参数就要我们自己配了,了解其构造方法势在必行。


ThreadPoolExecutor类一共有四个构造方法,我们只需要了解之中的一个就可以了,因为其他三种构造方法只是帮我们配置了一些默认参数,最后还是调用了它。


public ThreadPoolExecutor(int corePoolSize,
            int maximumPoolSize,
            long keepAliveTime,
            TimeUnit unit,
            BlockingQueue<Runnable> workQueue,
            ThreadFactory threadFactory,
            RejectedExecutionHandler handler)


其中的参数含义是:


corePoolSize:线程池中的线程数量;

maximumPoolSize:线程池中的最大线程数量;

keepAliveTime:当线程池线程数量超过corePoolSize时,多余的空闲线程会在多长时间内被销毁;

unit:keepAliveTime的时间单位;

workQueue:任务队列,被提交但是尚未被执行的任务;

threadFactory:线程工厂,用于创建线程,一般情况下使用默认的,即Executors类的静态方法defaultThreadFactory();handler:拒绝策略。当任务太多来不及处理时,如何拒绝任务。


对于这些参数要有以下了解:


corePoolSize与maximumPoolSize的关系


首先corePoolSize肯定是 <= maximumPoolSize。


其他关系如下:


若当前线程池中线程数 < corePoolSize,则每来一个任务就创建一个线程去执行;

若当前线程池中线程数 >= corePoolSize,会尝试将任务添加到任务队列。如果添加成功,则任务会等待空闲线程将其取出并执行;

若队列已满,且当前线程池中线程数 < maximumPoolSize,创建新的线程;

若当前线程池中线程数 >= maximumPoolSize,则会采用拒绝策略(JDK提供了四种,下面会介绍到)。

注意:关系3是针对的有界队列,无界队列永远都不会满,所以只有前2种关系。


workQueue


参数workQueue是指提交但未执行的任务队列。若当前线程池中线程数>=corePoolSize时,就会尝试将任务添加到任务队列中。主要有以下几种:


SynchronousQueue:直接提交队列。SynchronousQueue没有容量,所以实际上提交的任务不会被添加到任务队列,总是将新任务提交给线程执行,如果没有空闲的线程,则尝试创建新的线程,如果线程数量已经达到最大值(maximumPoolSize),则执行拒绝策略。

LinkedBlockingQueue:无界的任务队列。当有新的任务来到时,若系统的线程数小于corePoolSize,线程池会创建新的线程执行任务;当系统的线程数量等于corePoolSize后,因为是无界的任务队列,总是能成功将任务添加到任务队列中,所以线程数量不再增加。若任务创建的速度远大于任务处理的速度,无界队列会快速增长,直到内存耗尽。


handler


JDK内置了四种拒绝策略:


  • DiscardOldestPolicy策略:丢弃任务队列中最早添加的任务,并尝试提交当前任务;
  • CallerRunsPolicy策略:调用主线程执行被拒绝的任务,这提供了一种简单的反馈控制机制,将降低新任务的提交速度。
  • DiscardPolicy策略:默默丢弃无法处理的任务,不予任何处理。
  • AbortPolicy策略:直接抛出异常,阻止系统正常工作。


至此,我们直接new ThreadPoolExecutor类就不用慌了!!!!


相关文章
|
4月前
|
缓存 安全 Java
多线程线程池问题之为什么手动创建的线程池比使用Executors类提供的线程池更安全
多线程线程池问题之为什么手动创建的线程池比使用Executors类提供的线程池更安全
|
5月前
|
缓存 并行计算 Java
重温JAVA线程池精髓:Executor、ExecutorService及Executors的源码剖析与应用指南
重温JAVA线程池精髓:Executor、ExecutorService及Executors的源码剖析与应用指南
|
5月前
|
存储 缓存 Java
如何创建一个线程池,为什么不推荐使用Executors去创建呢?
【6月更文挑战第2天】如何创建一个线程池,为什么不推荐使用Executors去创建呢?
42 1
|
6月前
|
Java API 开发者
为什么禁止使用Executors创建线程池?
【5月更文挑战第5天】`Executors`类在Java中广泛用于创建线程池,但建议避免使用,因其线程池大小固定,不适用于所有场景;线程池生命周期管理不便,无法优雅关闭;异常处理机制不足,抛出的未检查异常会被忽略,影响调试;且资源管理较为手动,增加代码复杂性。尽管如此,对于简单应用和原型设计,`Executors`仍有其价值。在复杂情况下,推荐使用更灵活的`ThreadPoolExecutor`。
80 1
|
缓存 Java p3c
【Java用法】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
【Java用法】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
188 0
|
6月前
|
Java
Executors创建线程池
Executors创建线程池
|
6月前
|
存储 Java 开发者
为什么建议不要使用Executors来创建线程池?
为什么建议不要使用Executors来创建线程池?
59 0
|
6月前
|
安全 Java
线程池初始化严禁使用Executors
线程池初始化严禁使用Executors
|
Java
Java 利用JUC CountDownLatch 线程池Executors 实现多线程操作
Java 利用JUC CountDownLatch 线程池Executors 实现多线程操作 业务场景:某个业务操作非常耗时,但又必须等这个操作结束后才能进行后续操作
76 0
ThreadPoolExecutor的中的submit和FutureTask || 通过Executors 创建线程池的一些实例(Callable和Runnable的在其中的体现)
ThreadPoolExecutor的中的submit和FutureTask || 通过Executors 创建线程池的一些实例(Callable和Runnable的在其中的体现)
203 0