线程池为什么不允许使用Executors创建

合理利用线程池能够带来三个好处

第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

第二:提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。但是要做到合理的利用线程池,必须对其原理了如指掌

线程池的主要工作流程

 执行逻辑说明:

复制代码
判断核心线程数是否已满,核心线程数大小和corePoolSize参数有关,未满则创建线程执行任务

若核心线程池已满,判断队列是否满,队列是否满和workQueue参数有关,若未满则加入队列中

若队列已满,判断线程池是否已满,线程池是否已满和maximumPoolSize参数有关,若未满创建线程执行任务

若线程池已满,则采用拒绝策略处理无法执执行的任务,拒绝策略和handler参数有关
复制代码

线程池的创建

 我们可以通过ThreadPoolExecutor来创建一个线程池。

new ThreadPoolExecutor(corePoolSize, maximumPoolSize,
keepAliveTime, milliseconds,runnableTaskQueue, threadFactory,handler);

创建一个线程池需要输入几个参数:

复制代码
corePoolSize - 线程池核心池的大小。
maximumPoolSize - 线程池的最大线程数。
keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
unit - keepAliveTime 的时间单位。
workQueue - 用来储存等待执行任务的队列。
threadFactory - 线程工厂。
handler - 拒绝策略。
复制代码

参数的英文解释:

复制代码
* @param corePoolSize the number of threads to keep in the pool, even
*        if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
*        pool
* @param keepAliveTime when the number of threads is greater than
*        the core, this is the maximum time that excess idle threads
*        will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
*        executed.  This queue will hold only the {@code Runnable}
*        tasks submitted by the {@code execute} method.
复制代码

轻松理解corePoolSize与maxPoolSize:

我们可以把 corePoolSize 与 maxPoolSize 比喻成长工与临时工,通常古代一个大户人家会有几个固定的长工,负责日常的工作,而大户人家起初肯定也是从零开始雇佣长工的。
假如长工数量被老爷设定为 5 人,也就对应了 corePoolSize,不管这 5 个长工是忙碌还是空闲,都会一直在大户人家待着,可到了农忙或春节,长工的人手显然就不够用了,这时就需要雇佣更多的临时工,这些临时工就相当于在 corePoolSize 的基础上继续创建新线程,但临时工也是有上限的,也就对应了maxPoolSize,随着农忙或春节结束,老爷考虑到人工成本便会解约掉这些临时工,家里工人数量便会从maxPoolSize降到corePoolSize,所以老爷家的工人数量会一致保持在corePoolSize 和 maxPoolSize 的区间。

为什么线程池不允许使用Executors去创建

阿里巴巴java开发手册有这么一句话:“线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险”

 

 Executors创建返回ThreadPoolExecutor对象的方法共有三种:

Executors#newCachedThreadPool => 创建可缓存的线程池
Executors#newSingleThreadExecutor => 创建单线程的线程池
Executors#newFixedThreadPool => 创建固定长度的线程池

Executors#newCachedThreadPool方法

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

CachedThreadPool是一个根据需要创建新线程的线程池

corePoolSize => 0,核心线程池的数量为0
maximumPoolSize => Integer.MAX_VALUE,可以认为最大线程数是无限的
keepAliveTime => 60L
unit => 秒
workQueue => SynchronousQueue

当一个任务提交时,corePoolSize为0不创建核心线程,SynchronousQueue是一个不存储元素的队列,可以理解为队里永远是满的,因此最终会创建非核心线程来执行任务。

对于非核心线程空闲60s时将被回收。因为Integer.MAX_VALUE非常大,可以认为是可以无限创建线程的,在资源有限的情况下容易引起OOM异常

Executors#newSingleThreadExecutor方法

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

SingleThreadExecutor是单线程线程池,只有一个核心线程

corePoolSize => 1,核心线程池的数量为1
maximumPoolSize => 1,只可以创建一个非核心线程
keepAliveTime => 0L
unit => 秒
workQueue => LinkedBlockingQueue

当一个任务提交时,首先会创建一个核心线程来执行任务,如果超过核心线程的数量,将会放入队列中,因为LinkedBlockingQueue是长度为Integer.MAX_VALUE的队列,可以认为是无界队列,因此往队列中可以插入无限多的任务,在资源有限的时候容易引起OOM异常,同时因为无界队列,maximumPoolSize和keepAliveTime参数将无效,压根就不会创建非核心线程

Executors#newFixedThreadPool方法

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

FixedThreadPool是固定核心线程的线程池,固定核心线程数由用户传入

复制代码
corePoolSize => 1,核心线程池的数量为1
maximumPoolSize => 1,只可以创建一个非核心线程
keepAliveTime => 0L
unit => 秒
workQueue => LinkedBlockingQueue

它和SingleThreadExecutor类似,唯一的区别就是核心线程数不同,并且由于使用的是LinkedBlockingQueue,在资源有限的时候容易引起OOM异常
复制代码

总结:

FixedThreadPool和SingleThreadExecutor => 允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而引起OOM异常

CachedThreadPool => 允许创建的线程数为Integer.MAX_VALUE,可能会创建大量的线程,从而引起OOM异常

这就是为什么禁止使用Executors去创建线程池,而是推荐自己去创建ThreadPoolExecutor的原因

 

线程池创建方式(推荐)

根据阿里巴巴java开发规范,推荐了3种线程池创建方式     

推荐方式1:
   首先引入:commons-lang3包

  ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
        new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());

推荐方式 2:
  首先引入:com.google.guava包

复制代码
    ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
        .setNameFormat("demo-pool-%d").build();
 
    //Common Thread Pool
    ExecutorService pool = new ThreadPoolExecutor(5, 200,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
 
    pool.execute(()-> System.out.println(Thread.currentThread().getName()));
    pool.shutdown();//gracefully shutdown
复制代码

推荐方式 3:
   spring配置线程池方式:自定义线程工厂bean需要实现ThreadFactory,可参考该接口的其它默认实现类,使用方式直接注入bean
调用execute(Runnable task)方法即可

复制代码
<bean id="userThreadPool"
        class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        <property name="corePoolSize" value="10" />
        <property name="maxPoolSize" value="100" />
        <property name="queueCapacity" value="2000" />
 
    <property name="threadFactory" value= threadFactory />
        <property name="rejectedExecutionHandler">
            <ref local="rejectedExecutionHandler" />
        </property>
    </bean>
    //in code
    userThreadPool.execute(thread);
复制代码
posted @ 2021-01-22 09:30  路要一步一步走  阅读(441)  评论(0编辑  收藏  举报