• 为什么阿里的Java开发规范中禁止使用Executors创建线程池?


    一. 问题概述

    最近壹哥有个学生出去面试,面试官的一个问题是:在开发中你使用什么方式创建线程池?

    这个学生答曰:使用jdk中自带的工厂类Executors 创建线程池该学生回答完问题后,感觉面试官对此答案不是很满意,于是就跑回来问壹哥。那么接下来,壹哥就和大家来分享一下这道面试题的标准答案。 

    二. 问题答案

    其实这个问题的答案,在阿里巴巴开发规范1.4版中,早就有明确的答案,我们先来看看阿里巴巴开发规范中对于这一段描述的截图:

    从上图中我们不难看出,创建线程池,最好直接使用 ThreadPoolExecutor 类的构造方法创建那么为什么最好要使用ThreadPoolExecutor类创建呢?阿里巴巴的开发规范中交代得还不是非常的清楚,接下来壹哥就为大家仔细的分析一下。 

    三. 问题解析

    1. 线程池的工作原理

    其实我们就算是使用工具类Executors来创建线程池,最后还得调用 ThreadPoolExecutor 类的构造方法来创建线程池对象,ThreadPoolExecutor 的构造方法的源代码如下:

    1. /**
    2. * 参数说明:
    3. * corePoolSize: 核心线程数
    4. * maximumPoolSize :线程池最大数量
    5. * keepAliveTime: 空闲线程的存活时间
    6. * unit: 存活时间的单位
    7. * workQueue: 阻塞队列
    8. * threadFactory: 线程工厂
    9. * handler: 拒绝策略
    10. */
    11. public ThreadPoolExecutor(int corePoolSize,
    12. int maximumPoolSize,
    13. long keepAliveTime,
    14. TimeUnit unit,
    15. BlockingQueue<Runnable> workQueue,
    16. ThreadFactory threadFactory,
    17. RejectedExecutionHandler handler)

    线程池的执行流程图及文字说明如下图所示:

    根据上图,我们可知线程池的执行机制如下:

    1. 提交一个任务,首先判断核心线程数(corePoolSize)是否已经满了。没满就创建线程执行任务;
    2. 如果核心线程已经满了,就要判断阻塞队列(workQueue)是否已经满了,未满就放入阻塞队列当中;
    3. 如果阻塞队列也满了,那就判断最大线程数(maximumPoolSize)是否满了,没满就创建线程执;
    4. 如果连最大线程数也满了,那么使用拒绝策略(handler)处理这一次任务。

    【重点】

    从执行流程图中,我们不难看出只有阻塞队列和最大线程数都满了,线程池才会拒绝任务,所以阻塞队列的长度阈值和最大线程数的阈值就很重要了

    如果阻塞队列(workQueue)的长度过长,可能会堆积大量的请求,造成OOM如果最大线程数(maximumPoolSize)过大,就要可能创建大量的线程,同样也会造成OOM

    2. 为什么不能使用Executors创建线程池

    咱们使用Executors工厂类创建的线程池一般分为以下三类。

    2.1 Executors.newCachedThreadPool() // 创建可缓存的线程池 源代码如下:

    1. // 我们可以看到最大线程数maximumPoolSize的取值是 Integer.MAX_VALUE
    2. // 这样容易创建大量线程造成OOM
    3. public static ExecutorService newCachedThreadPool() {
    4. return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
    5. 60L, TimeUnit.SECONDS,
    6. new SynchronousQueue<Runnable>());
    7. }

     2.2 Executors.newSingleThreadExecutor() // 创建单线程的线程池,源代码如下:

    1. // 这里我们注意到虽然maximumPoolSize的取值是1了,
    2. // 但是阻塞队列使用了LinkedBlockingQueue,我们接着看LinkedBlockingQueue的源代码
    3. public static ExecutorService newSingleThreadExecutor() {
    4. return new FinalizableDelegatedExecutorService
    5. (new ThreadPoolExecutor(1, 1,
    6. 0L, TimeUnit.MILLISECONDS,
    7. new LinkedBlockingQueue<Runnable>()));
    8. }
    9. // 这是LinkedBlockingQueue的源代码,从这里不难看出阻塞队列的长度过长,也容易造成oom
    10. public LinkedBlockingQueue() {
    11. this(Integer.MAX_VALUE);
    12. }

     2.3 Executors.newFixedThreadPool() // 创建固定长度的线程池,源代码如下:

    1. // 这里和newSingleThreadExecutor的问题是一样的,阻塞队列长度过长,造成oom
    2. public static ExecutorService newFixedThreadPool(int nThreads) {
    3. return new ThreadPoolExecutor(nThreads, nThreads,
    4. 0L, TimeUnit.MILLISECONDS,
    5. new LinkedBlockingQueue<Runnable>());
    6. }

    小结

    从上面的分析我们不难看出,使用Executors创建的线程池,可能会存在阻塞队列的长度过长或者最大线程数过大的问题,有造成OOM的隐患

    3. 创建线程池的正确姿势

    看到这里,也许有些同学已经会迫不及待的说,既然禁止使用Executors创建线程池,那么以后就直接使用ThreadPoolExecutor 的构造方法创建线程池不就完了吗?可是大家是否考虑过,如果使用ThreadPoolExecutor 创建线程池,那么最大线程数(maximumPoolSize),阻塞队列的默认长度又应该设置为多少合适呢?

  • 相关阅读:
    x264 参考帧管理原理:reference_build_list 函数
    实时美颜技术的崭新时代:美颜SDK开发与应用
    nodejs校园二手交易管理系统vue
    flutter3-weos手机OS系统|Flutter3.22+Getx仿ios桌面管理OA应用
    多功能手持读数仪VH03如何连接手机蓝牙
    Redis 中 Set 数据结构详解
    IIC基础知识
    Java SE 9 多版本兼容 JAR 包示例
    主应用窗口
    Python爬虫实战第三例【三】(下)
  • 原文地址:https://blog.csdn.net/syc000666/article/details/125534323