大厂面试丨为什么阿里的Java开发规范,禁止使用Executors创建线程池?!

一. 问题概述

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

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

二. 问题的答案

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

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

三. 问题解析

3.1 线程池的工作原理

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

/**
* 参数说明:
* corePoolSize: 核心线程数
* maximumPoolSize :线程池最大数量
* keepAliveTime: 空闲线程的存活时间
* unit: 存活时间的单位
* workQueue: 阻塞队列
* threadFactory: 线程工厂
* handler: 拒绝策略
*/
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

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

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

  • 提交一个任务,首先判断核心线程数(corePoolSize)是否已经满了。没满就创建线程执行任务;

  • 如果核心线程已经满了,就要判断阻塞队列(workQueue)是否已经满了,未满就放入阻塞队列当中;

  • 如果阻塞队列也满了,那就判断最大线程数(maximumPoolSize)是否满了,没满就创建线程值;

  • 如果连最大线程数也满了,那么使用拒绝策略(handler)处理这一次任务。

【重点】

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

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

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

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

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

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

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

// 这里我们注意到虽然maximumPoolSize的取值是1了,
// 但是阻塞队列使用了LinkedBlockingQueue,我们接着看LinkedBlockingQueue的源代码
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

// 这是LinkedBlockingQueue的源代码,从这里不难看出阻塞队列的长度过长,也容易造成oom
public LinkedBlockingQueue() {
    this(Integer.MAX_VALUE);
}

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

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

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

3.3 创建线程池的正确姿势

看到这里,也许有些同学已经会迫不及待的说,既然禁止使用Executors创建线程池,那么以后就直接使用ThreadPoolExecutor 的构造方法创建线程池不就完了吗?

可是大家是否考虑过,如果使用ThreadPoolExecutor 创建线程池,那么最大线程数(maximumPoolSize),阻塞队列的默认长度又应该设置为多少合适呢?

这个答案耀哥将会在以后的文章中揭晓,欢迎大家继续关注!

四. 后记

最后耀哥给大家分享一个交流群,大家可以扫描下图中的二维码,领取海量免费学习资料!

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

马剑威(威哥爱编程)

你的鼓励是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值