启用Executor初始化线程池
前言
上文我们介绍了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来创建线程池,那我们先来回顾一下Executors和ThreadPoolExecutor。

我们可以看到ThreadPoolExecutor已经是Executor的具体实现了,而且具有较多可配参数(可配参数见下方,可仅了解,用到时再进行详细查询)。Executors是一个创建线程池的工具类,查看其源码的话也会发现这几种创建线程池的方法也都是通过调用ThreadPoolExecutor来实现的。
ThreadPoolExecutor一共有四个构造函数,七个可配参数,分别是
- corePoolSize: 线程池中保持存活线程的数量。
- maximumPoolSize: 线程池中允许线程数量的最大值
- keepAliveTime: 表示线程没有任务执行时最多保持多久时间会终止
- unit: 参数keepAliveTime的时间单位
- workQueue: 一个阻塞队列,用来存储等待执行的任务
- threadFactory: 线程工厂,主要用来创建线程
- 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错误。
上面提到的问题主要体现在newFixedThreadPool和newSingleThreadExecutor两个工厂方法上,并不是说newCachedThreadPool和newScheduledThreadPool这两个方法就安全了,这两种方式创建的最大线程数可能是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初始化线程池的更多相关文章
- 多线程-Executors和Executor,线程池
jdk1.5之前,所有的线程都是需要自己手动创建的,由jvm销毁,当请求过多的时候,频繁的创建和销毁线程是非常浪费资源的.jdk1.5为此做了优化,提供了 java.util.concurrent 包 ...
- 由初始化线程池引发的NoClassDefFoundError 异常分析
今天说的异常是一个很不常见的异常,至少我不经常见到这个异常.首先先看下NoClassDefFoundError官方定义 : Java Virtual Machine is not able to fi ...
- Tomcat启动时加载数据到缓存---web.xml中listener加载顺序(例如顺序:1、初始化spring容器,2、初始化线程池,3、加载业务代码,将数据库中数据加载到内存中)
最近公司要做功能迁移,原来的后台使用的Netty,现在要迁移到在uap上,也就是说所有后台的代码不能通过netty写的加载顺序加载了. 问题就来了,怎样让迁移到tomcat的代码按照原来的加载顺序进行 ...
- JAVA Executor(线程池)框架
一.Executor概述 为更好控制线程,jdk提供一套线程管理框架Executor,帮助开发人员有效地进行线程控制.它们都位于java.util.concurrent包中,是jdk并发包的核心.其中 ...
- 戏(细)说Executor框架线程池任务执行全过程(上)
一.前言 1.5后引入的Executor框架的最大优点是把任务的提交和执行解耦.要执行任务的人只需把Task描述清楚,然后提交即可.这个Task是怎么被执行的,被谁执行的,什么时候执行的,提交的人就不 ...
- 戏(细)说Executor框架线程池任务执行全过程(下)
上一篇文章中通过引入的一个例子介绍了在Executor框架下,提交一个任务的过程,这个过程就像我们老大的老大要找个老大来执行一个任务那样简单.并通过剖析ExecutorService的一种经典实现Th ...
- Executor以及线程池
在应用程序中,总是会出现大量的任务,包括相同类型的和不同类型的.要快速处理这些任务,常见方法就是利用多线程,但是也不可能为每个任务都创建一个线程,这样内存也不够,并且线程的创建销毁开销很大.最好是少量 ...
- Java 并发 —— Thread、Executor、线程池
Java 线程池: ThreadPoolExecutor,创建此线程池的方法: new Executors.newCachedThreadPool():尽量避免使用,其无法控制线程数量, Schedu ...
- Java并发编程的艺术(十一)——Executor与线程池
Executor框架简介 从JDK5开始,把工作单元和执行机制分离开来了,工作的单元包括Runnable和Callable,执行机制就是由Executor框架提供. Executor两级调度模型 Ho ...
随机推荐
- linxu passwd 给linux用户设置密码 命令
[root@localhost ~]# passwd # 修改 root 用户的密码 passwd 给linux用户设置密码 命令 passwd www 直接passwd是当前用户设置密码 非交互式修 ...
- POJ 2253 Frogger(dijkstra 最短路
POJ 2253 Frogger Freddy Frog is sitting on a stone in the middle of a lake. Suddenly he notices Fion ...
- c++ try_throw_catch异常处理
参考https://www.cnblogs.com/xiaojianliu/articles/8900795.html 在程序设计时,针对不同的异常情况,预先设定异常信息,在程序运行时,根据异常提示信 ...
- 如何用vue封装一个防用户删除的平铺页面的水印组件
需求 为了防止截图等安全问题,在web项目页面中生成一个平铺全屏的水印 要求水印内容为用户名,水印节点用户不能通过开发者工具等删除 效果 如上图 在body节点下插入水印DOM节点,水印节点覆盖在页面 ...
- 剑指offer-递归和循环-python
-斐波那契数列- 大家都知道斐波那契数列(1.1.2.3.5.8.13.21.34.……),现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0). n<=39 斐波那契数 ...
- 【测试环境】TCPCopy 使用方法
https://blog.csdn.net/ronmy/article/details/65657691 TCPCopy是一种请求复制(所有基于tcp的packets)工具,可以把在线请求导入到测试系 ...
- oracle的基本情况和一些基本概念
Oracle Database,又名Oracle RDBMS,或简称Oracle.是甲骨文公司的一款关系数据库管理系统.它是在数据库领域一直处于领先地位的产品.可以说Oracle数据库系统是目前世界上 ...
- 解决引入 lombok 注解不生效,Eclipse与IDEA集成 lombok
Eclipse -javaagent:lombok.jar -vmargs -javaagent:lombok.jar IDEA 添加依赖 <!--lombok 实体类注解--> < ...
- Delphi 循环语句和程序的循环结构
- python virtualenv和virtualenv的使用
首先下面的步骤我都是在windows下执行,Python2.7,pip==19.1.1 1.安装virtualenv pip install virtualenv 2.新建virtualenv vir ...