前言

上文我们介绍了JDK中的线程池框架Executor。我们知道,只要需要创建线程的情况下,即使是在单线程模式下,我们也要尽量使用Executor。即:

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(1);
//此处不该利用Executors工具类来初始化线程池

但是,在《阿里巴巴Java开发手册》中有一条

【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

Executors 返回的线程池对象的弊端如下:

FixedThreadPool 和 SingleThreadPool : 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。

CachedThreadPool 和 ScheduledThreadPool : 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

可以看到,这是一个强制性的规则,并且是不允许使用Executors来创建,建议使用ThreadPoolExecutor来创建线程池,那我们先来回顾一下ExecutorsThreadPoolExecutor

我们可以看到ThreadPoolExecutor已经是Executor的具体实现了,而且具有较多可配参数(可配参数见下方,可仅了解,用到时再进行详细查询)。Executors是一个创建线程池的工具类,查看其源码的话也会发现这几种创建线程池的方法也都是通过调用ThreadPoolExecutor来实现的。

ThreadPoolExecutor一共有四个构造函数,七个可配参数,分别是

  1. corePoolSize: 线程池中保持存活线程的数量。
  2. maximumPoolSize: 线程池中允许线程数量的最大值
  3. keepAliveTime: 表示线程没有任务执行时最多保持多久时间会终止
  4. unit: 参数keepAliveTime的时间单位
  5. workQueue: 一个阻塞队列,用来存储等待执行的任务
  6. threadFactory: 线程工厂,主要用来创建线程
  7. handler:表示当拒绝处理任务时的策略

分析

那么Executors到底会导致什么问题,才会让开发手册中直接被定义为不允许了呢。首先就是一个血淋淋的教训,直接导致线上服务不可用,已经可以算是事故了。

实验

我们也可以现在我们本地进行一下小实验:

public class ExecutorsTesting {
private static ExecutorService executor = Executors.newFixedThreadPool(15);
public static void main(String[] args) {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
executor.execute(new SubThread());
}
}
} class SubThread implements Runnable {
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
//do nothing
}
}
}

运行时指定JVM参数:-Xmx8m -Xms8m,大概几秒钟之后,会报出OOM错误:

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at com.kaikeba.mybatis.ExecutorsTesting.main(ExecutorsTesting.java:10)
//报错行数为上述代码中的executor.execute(new SubThread());

那么为什么会报出这个错误呢。

源码分析

我们先来看一下Executors中的FixedThreadPool是如何构造的。

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

可以看到对于存储等待执行的任务,FixedThreadPool是通过LinkedBlockingQueue来实现的。而我们知道LinkedBlockingQueue是一个链表实现的阻塞队列,而如果不设置其容量的话,将会是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE由于Executors中并未设置容量,所以应用可以不断向队列中添加任务,导致OOM错误

上面提到的问题主要体现在newFixedThreadPoolnewSingleThreadExecutor两个工厂方法上,并不是说newCachedThreadPoolnewScheduledThreadPool这两个方法就安全了,这两种方式创建的最大线程数可能是Integer.MAX_VALUE,而创建这么多线程,必然就有可能导致OOM。

如何该利用ThreadPoolExecutor来创建线程池呢?

我们其实可以看到Executors中的newFixedThreadPool其实也是调用ThreadPoolExecutor来实现的。正如手册中所说,当我们不用Executors默认创建线程池的方法,而直接自己手动去调用ThreadPoolExecutor,可以让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。比如我们在Executors.newFixedThreadPool基础上给LinkedBlockingQueue加一个容量,当队列已经满了,而仍需要添加新的请求会抛出相应异常,我们可以根据异常做相应处理。

public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(10)); //添加容量大小
}

除了自己定义ThreadPoolExecutor外。还可以利用其它开源类库,如apache和guava等,可以有更多个性化配置。

参考文章:

https://www.hollischuang.com/archives/2888

https://stackoverflow.com/questions/1094867/when-should-we-use-javas-thread-over-executor#answer-34373289

https://blog.51cto.com/zero01/2306857


本文由博客一文多发平台 OpenWrite 发布!

文章首发:https://zhuanlan.zhihu.com/lovebell

个人公众号:技术Go

您的点赞与支持是作者持续更新的最大动力!

【搞定面试官】你还在用Executors来创建线程池?会有什么问题呢?的更多相关文章

  1. 【搞定面试官】谈谈你对JDK中Executor的理解?

    ## 前言 随着当今处理器计算能力愈发强大,可用的核心数量越来越多,各个应用对其实现更高吞吐量的需求的不断增长,多线程 API 变得非常流行.在此背景下,Java自JDK1.5 提供了自己的多线程框架 ...

  2. 搞定面试官 - MySQL 中你知道如何计算一个索引的长度嘛?

    大家好,我是程序员啊粥. 今天给大家分享一个我遇到过的比较少见的面试题,那就是 MySQL 中如何计算一个索引的长度. 说实话,我第一次遇到这个问题的时候想当然的以为索引长度就是我们建表时定义的字段长 ...

  3. 搞定面试官 - 你可以介绍一下在 MySQL 中,哪些情况下 索引会失效嘛?

    大家好,我是程序员啊粥,前边给大家分享了 *MySQL InnoDB 索引模型 在 MySQL InnoDB 中,为什么 delete 删除数据之后表数据文件大小没有变 如何计算一个索引的长度 如何查 ...

  4. 【搞定面试官】try中有return,finally还会执行吗?

    本篇文章我们主要探讨 一下如果try {}语句中有return,这种情况下finally语句还会执行吗?其实JVM规范是对这种情况有特殊规定的,那我就先上代码吧! public class Final ...

  5. 金三银四,2018最新iOS面试题,由它可以搞定面试官?

    序言 这些资料,你一定会用到!我相信很多人都在说,iOS行业不好了,iOS现在行情越来越难了,失业的人比找工作的人还要多.失业即相当于转行,跳槽即相当于降低自己的身价.那么做iOS开发的你,你是否在时 ...

  6. 【搞定面试官】- Synchronized如何实现同步?锁优化?(1)

    前言 说起Java面试中最高频的知识点非多线程莫属.每每提起多线程都绕不过一个Java关键字--synchronized.我们都知道该关键字可以保证在同一时刻,只有一个线程可以执行某个方法或者某个代码 ...

  7. 搞定面试官:咱们从头到尾再说一次 Java 垃圾回收

    接着前几天的两篇文章,继续解析JVM面试问题,送给年后想要跳槽的小伙伴 万万没想到,面试中,连 ClassLoader类加载器 也能问出这么多问题..... 万万没想到,JVM内存区域的面试题也可以问 ...

  8. RabbitMQ:从入门到搞定面试官

    安装 使用docker安装,注意要安装tag后缀为management的镜像(包含web管理插件),我这里使用的是rabbitmq:3.8-management 1. 拉取镜像 shell docke ...

  9. 搞定面试官 - 可以介绍一下在 MySQL 中你平时是怎么使用 COUNT() 的嘛?

    大家好,我是程序员啊粥. 相信在大家的工作中,有很多的功能都需要用到 count(*) 来统计表中的数据行数.同时,对于一些大数据的表,用 count 都是瑟瑟发抖,往往会结合缓存等进行处理. 那么, ...

随机推荐

  1. iOS开发高级分享 - iOS上的设备标识符和指纹

    苹果认可的标识符 Apple提供了各种API,以方便用户识别各种用途: 通用标识符(UDID) 在iOS的早期,苹果公司提供了一个uniqueIdentifier财产上UIDevice-亲切地称为ud ...

  2. net core WebApi——公用库April.Util公开及发布

    前言 在之前鼓捣过一次基础工程April.WebApi后,就考虑把常用的类库打包做成一个公共类库,这样既方便维护也方便后续做快速开发使用,仓库地址:April.Util_github,April.Ut ...

  3. 2、linu

    一.常用linux命令 昨日内容回顾 linux基本命令 ls 查看目录和文件ls -la 查看所有文件和目录详情(包括隐藏文件, -l和-a可以单独使用)mkdirrmdirtouchcatcdrm ...

  4. Vue+element UI实现分页组件

    介绍 这是一个是基于element-UI的分页组件基础上,进行了二次封装的分页组件,在展示数据时,该分页组件采用了每显示一页数据,只请求当前页面的数据的请求策略,从而避免了一次性将数据全部请求所造成的 ...

  5. 深入理解java虚拟机系列初篇(一):为什么要学习JVM?

    前言 本来想着关于写JVM这个专栏,直接写知识点干货的,但是想着还是有必要开篇讲一下为什么要学习JVM,这样的话让一些学习者心里有点底的感觉比较好... 原因一:面试 不得不说,随着互联网门槛越来越高 ...

  6. Hybrid App: 了解JavaScript如何与Native实现混合开发

    一.简介 Hybrid Development混合开发是目前移动端开发异常火热的新兴技术,它能够实现跨平台开发,极大地节约了人力和资源成本.跨平台开发催生了很多新的开源框架,就目前而言,在混合开发中比 ...

  7. 『题解』洛谷P4016 负载平衡问题

    title: categories: tags: - mathjax: true --- Problem Portal Portal1:Luogu Portal2: LibreOJ Descripti ...

  8. synchronized和ReentrantLock锁住了谁?

    一.synchronized 案例1: public class LockDemo{ public static void main(String[] args) throws Exception { ...

  9. jquery mobiscroll移动端日期选择控件(无乱码)

    jquery mobiscroll移动端日期选择控件(无乱码) <pre><!DOCTYPE html><html lang="en">< ...

  10. Apache Spark 3.0 预览版正式发布,多项重大功能发布

    2019年11月08日 数砖的 Xingbo Jiang 大佬给社区发了一封邮件,宣布 Apache Spark 3.0 预览版正式发布,这个版本主要是为了对即将发布的 Apache Spark 3. ...