zoukankan      html  css  js  c++  java
  • 线程池为什么不允许使用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);
  • 相关阅读:
    c++ 虚继承与继承的差异 (转)
    主题:PageRank解释
    (转)开源爬虫larbin分析
    Django随笔
    原生爬虫小Demo
    SVN
    Python的正则表达式与JSON
    类库 方法 模块等
    笔记
    自动补全Typeahead
  • 原文地址:https://www.cnblogs.com/zz-ksw/p/12426940.html
Copyright © 2011-2022 走看看