首页 > 编程语言 >为什么阿里的Java开发规范中禁止使用Executors创建线程池?

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

时间:2022-12-26 12:05:49浏览次数:36  
标签:Java Executors 队列 创建 阻塞 线程 ThreadPoolExecutor


一. 问题概述

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

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

二. 问题答案

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

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

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

三. 问题解析

1. 线程池的工作原理

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

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

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

为什么阿里的Java开发规范中禁止使用Executors创建线程池?_构造方法_02

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

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

【重点】

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

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

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

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

2.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.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);
}

 2.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. 创建线程池的正确姿势

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

标签:Java,Executors,队列,创建,阻塞,线程,ThreadPoolExecutor
From: https://blog.51cto.com/yyg666/5968701

相关文章

  • 高薪程序员&Java面试题精讲系列汇总
    因为没有分类归纳博客的功能,所以特写本帖汇总《高薪程序员面试题精讲系列教程》,方便大家查阅!希望各位小伙伴,可以从我的拙作中能对Java的高频面试题有所掌握,也希望各位可以多......
  • Linux系统上Java单体项目崩溃自启动脚本(通用型)
    本文转自https://blog.csdn.net/qq_38374397/article/details/127566529实现对进程的监控这里只需要三步:设置服务启动脚本设置监控shell脚本设置linux周期定时执行指令......
  • Java代码打包
    Java代码打包一:IDEA工具1、右侧的maven直接clean后package2、终端命令mvncleanpackage3、项目结构,创建工件,选择清单属性,构建工件4、指定主类打包、含多个类的jar打......
  • Java基础之常用类(String类)
    String类定义String类代表字符串。Java程序中的所有字符串字面值(如"abc")都作为此类的实例实现。我们可以将字符串看作是String,但是严格意义上来说,String还是......
  • javaScript 列表常用语法基础大全
    javascript数组常用方法1.push()=>语法,数组.push(数据)=>作用:向数组的末尾追加数据=>返回值:添加数据以后,返回新的数组2.pop()=>语法,数组.pop(数据)=>作用:删除......
  • Java:泛型方法、泛型类、泛型接口、类型通配符
    (目录)要求:JDK>=1.5泛型方法packagecom.example.demo;importjava.io.IOException;publicclassDemo{//泛型方法publicstatic<T>voidprintT(T......
  • Java编程思想17
    第二十一章:并发基本的线程机制  并发编程使我们可以将程序划分为多个分离的、独立运行的任务。通过使用多线程机制,这些独立任务(也被称为子任务)中的每一个都将由执行线程......
  • Java编程思想18
    从任务种产生返回值:Runnable是执行工作的独立任务,但是它不返回任何值。如果你希望在任务完成时能够返回一个值,那么可以实现Callable接口而不是Runnable接口。在JavaSE5中......
  • Java编程思想19
    共享受限资源1.不正确访问资源  考虑下面的例子,其中一个任务产生产生偶数,而其他任务消费这些数字。而这些消费者任务的唯一工作就是校验偶数的有效性packageconcurr......
  • 实现JNI的另一种方法:使用RegisterNatives方法传递和使用Java自定义类
    除了使用传统方法实现JNI外,也可以使用RegisterNatives实现JNI。和传统方法相比,使用RegisterNatives的好处有三点:1、C++中函数命名自由,不必像javah自动生成的函数声明那样,拘......