前言

上文我们介绍了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等,可以有更多个性化配置。

启用Executor初始化线程池的更多相关文章

  1. 多线程-Executors和Executor,线程池

    jdk1.5之前,所有的线程都是需要自己手动创建的,由jvm销毁,当请求过多的时候,频繁的创建和销毁线程是非常浪费资源的.jdk1.5为此做了优化,提供了 java.util.concurrent 包 ...

  2. 由初始化线程池引发的NoClassDefFoundError 异常分析

    今天说的异常是一个很不常见的异常,至少我不经常见到这个异常.首先先看下NoClassDefFoundError官方定义 : Java Virtual Machine is not able to fi ...

  3. Tomcat启动时加载数据到缓存---web.xml中listener加载顺序(例如顺序:1、初始化spring容器,2、初始化线程池,3、加载业务代码,将数据库中数据加载到内存中)

    最近公司要做功能迁移,原来的后台使用的Netty,现在要迁移到在uap上,也就是说所有后台的代码不能通过netty写的加载顺序加载了. 问题就来了,怎样让迁移到tomcat的代码按照原来的加载顺序进行 ...

  4. JAVA Executor(线程池)框架

    一.Executor概述 为更好控制线程,jdk提供一套线程管理框架Executor,帮助开发人员有效地进行线程控制.它们都位于java.util.concurrent包中,是jdk并发包的核心.其中 ...

  5. 戏(细)说Executor框架线程池任务执行全过程(上)

    一.前言 1.5后引入的Executor框架的最大优点是把任务的提交和执行解耦.要执行任务的人只需把Task描述清楚,然后提交即可.这个Task是怎么被执行的,被谁执行的,什么时候执行的,提交的人就不 ...

  6. 戏(细)说Executor框架线程池任务执行全过程(下)

    上一篇文章中通过引入的一个例子介绍了在Executor框架下,提交一个任务的过程,这个过程就像我们老大的老大要找个老大来执行一个任务那样简单.并通过剖析ExecutorService的一种经典实现Th ...

  7. Executor以及线程池

    在应用程序中,总是会出现大量的任务,包括相同类型的和不同类型的.要快速处理这些任务,常见方法就是利用多线程,但是也不可能为每个任务都创建一个线程,这样内存也不够,并且线程的创建销毁开销很大.最好是少量 ...

  8. Java 并发 —— Thread、Executor、线程池

    Java 线程池: ThreadPoolExecutor,创建此线程池的方法: new Executors.newCachedThreadPool():尽量避免使用,其无法控制线程数量, Schedu ...

  9. Java并发编程的艺术(十一)——Executor与线程池

    Executor框架简介 从JDK5开始,把工作单元和执行机制分离开来了,工作的单元包括Runnable和Callable,执行机制就是由Executor框架提供. Executor两级调度模型 Ho ...

随机推荐

  1. PostgreSQL通过解析日志,获取数据库增量变化,pg_recvlogical

    1.首先用该工具来看我们的日志变化,需要先将test_decoding插件编译并安装(进入contrib,编译安装即可) 创建一个slot: SELECT * FROM pg_create_logic ...

  2. Java实现龟兔赛跑

    闲极无聊,加上翻手机看到龟兔赛跑的词语,想到了可以通过java起两个线程来实现龟兔赛跑的实现. 代码实现其实很简单: 首先是乌龟类: 然后是兔子类: 最后是赛跑类: 接下里让我们看一下输出结果吧: 乌 ...

  3. 【MQ】为什么选择RocketMQ?

    一.前言 提到mq,可能很多朋友都有多耳闻,很多大公司都在使用这种技术.就小编而言,听说使用mq可以进行秒杀的操作,而且使用十分的方便,效率十分的高.以前小编也做过关于秒杀的技术,就是使用悲观锁对DA ...

  4. The library 'libhostpolicy.dylib' required to execute the application was not found in

    .NET Core应用程序需要runtimeconfig.json文件.此JSON文件配置运行时的选项.没有runtimeconfig.json文件,这将失败. > dotnet Program ...

  5. 6.jQuery之类操作不影响原先类,but原生js却是会影响

    注意jQuery和js的区别: <style> .one { width: 200px; height: 200px; background-color: pink; transition ...

  6. --解决Lock wait timeout exceeded; try restarting transaction

    --解决Lock wait timeout exceeded; try restarting transaction select * from information_schema.innodb_t ...

  7. 091、万能的数据收集器 Fluentd (2019-05-15 周三)

    参考https://www.cnblogs.com/CloudMan6/p/7798224.html   前面的ELK 中我们使用的是 Filebeat 收集Docker日志,利用的是默认的loggi ...

  8. springboot导包spring-boot-starter-parent出现错误

    <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot ...

  9. JavaSE基础:泛型

    泛型 1.引入 情景模式描述,假设完成一个学生的成绩的情况: 整数: math=80,english=70 小数: math=85.6,englisth=77.8 字符串: math="66 ...

  10. ld - GNU linker (连接器)

    总览 (SYNOPSIS) ld [-o output] objfile... [-Aarchitecture] [-b input-format] [-Bstatic] [-Bdynamic] [- ...