看阿里巴巴开发手册并发编程这块有一条:线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,通过源码分析禁用的原因。

线程池的优点

管理一组工作线程,通过线程池复用线程有以下几点优点:

  • 减少资源创建:减少内存开销,创建线程占用内存
  • 降低系统开销:创建线程需要时间,会延迟处理的请求
  • 提高稳定性:避免无限创建线程引起的OutOfMemoryError

Executors创建线程池的方式

根据返回的对象类型创建线程池可以分为三类:

  • 创建返回ThreadPoolExecutor对象
  • 创建返回ScheduleThreadPoolExecutor对象
  • 创建返回ForkJoinPool对象

本文只讨论创建返回ThreadPoolExecutor对象

ThreadPoolExecutor对象

在介绍Executors创建线程池方法前先介绍一下ThreadPoolExecutor,因为这些创建线程池的静态方法都是返回ThreadPoolExecutor对象,和我们手动创建ThreadPoolExecutor对象的区别就是我们不需要自己传构造函数的参数。

ThreadPoolExecutor的构造函数共有四个,但最终调用的都是同一个:

public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)

构造函数参数说明:

  • corePoolSize => 线程池核心线程数量
  • maximumPoolSize => 线程池最大数量
  • keepAliveTime => 空闲线程存活时间
  • unit => 时间单位
  • workQueue => 线程池所使用的缓冲队列
  • threadFactory => 线程池创建线程使用的工厂
  • handler => 线程池对拒绝任务的处理策略

线程池执行任务逻辑和线程池参数的关系

执行逻辑说明:

  • 判断核心线程数是否已满,核心线程数大小和corePoolSize参数有关,未满则创建线程执行任务
  • 若核心线程池已满,判断队列是否满,队列是否满和workQueue参数有关,若未满则加入队列中
  • 若队列已满,判断线程池是否已满,线程池是否已满和maximumPoolSize参数有关,若未满创建线程执行任务
  • 若线程池已满,则采用拒绝策略处理无法执执行的任务,拒绝策略和handler参数有关

Executors创建返回ThreadPoolExecutors对象

Executors创建返回ThreadPoolExecutor对象的方法共有三种:

  • Executors#newCachedThreadPool => 创建可缓存的线程池
  • Executors#newSingleThreadExecutor => 创建单线程的线程池
  • Executors#newFixedThreadPool => 创建固定长度的线程池

Executors的newCachedThreadPool方法

public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}

CachedThreadPool是一个根据需要创建新线程的线程池

  • corePoolSize => 0,核心线程池的数量为0
  • maximumPoolSize => Integer.MAX_VALUE,可以认为最大线程数是无限的
  • keepAliveTime => 60L
  • unit => 秒
  • workQueue => SynchronousQueue

当一个任务提交时,corePoolSize为0不创建核心线程,SynchronousQueue是一个不存储元素的队列,可以理解为队列永远是满的,因此最终会创建非核心线程来执行任务。

对于非核心线程空闲60s时将被回收。因为Integer.MAX_VALUE非常大,可以认为是可以无限创建线程的,在资源有限的情况下容易引起OOM异常

Executors的newSingleThreadExecutor方法

public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}

SingleThreadExecutor是单线程线程池,只有一个核心线程

  • corePoolSize => 1,核心线程池的数量为1
  • maximumPoolSize => 1,只可以创建一个非核心线程
  • keepAliveTime => 0L
  • unit =>毫 秒
  • workQueue => LinkedBlockingQueue

当一个任务提交时,首先会创建一个核心线程来执行任务,如果超过核心线程的数量,将会放入队列中,因为LinkedBlockingQueue是长度为Integer.MAX_VALUE的队列,可以认为是无界队列,因此往队列中可以插入无限多的任务,在资源有限的时候容易引起OOM异常,同时因为无界队列,maximumPoolSize和keepAliveTime参数将无效,压根就不会创建非核心线程

Executors的newFixedThreadPool方法

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

FixedThreadPool是固定核心线程的线程池,固定核心线程数由用户传入

  • corePoolSize => nThreads,核心线程池的数量为nThreads

  • maximumPoolSize => nThreads,可以创建nThreads个非核心线程

  • keepAliveTime => 0L

  • unit => 毫秒

  • workQueue => LinkedBlockingQueue

    它和SingleThreadExecutor类似,由于使用的是LinkedBlockingQueue,在资源有限的时候容易引起OOM异常

这就是为什么禁止使用Executors去创建线程池,而是推荐自己去创建ThreadPoolExecutor的原因

OOM异常测试

理论上会出现OOM异常,必须测试一波验证之前的说法:

测试类:TaskTest.java

public class TaskTest {
public static void main(String[] args) {
ExecutorService es = Executors.newCachedThreadPool();
int i = 0;
while (true) {
es.submit(new Task(i++));
}
}
}

使用Executors创建的CachedThreadPool,往线程池中无限添加线程

在启动测试类之前先将JVM内存调整小一点,不然很容易将电脑跑出问题【别问我为什么知道,是铁憨憨甜没错了!!!】,在idea里:Run -> Edit Configurations

JVM参数说明:

  • -Xms10M => Java Heap内存初始化值
  • -Xmx10M => Java Heap内存最大值

运行结果:

Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"Disconnected from the target VM, address: '127.0.0.1:60416', transport: 'socket'

创建到3w多个线程的时候开始报OOM错误

另外两个线程池就不做测试了,测试方法一致,只是创建的线程池不一样

如何定义线程池参数

CPU密集型:线程池的大小推荐为CPU数量+1,CPU数量可以根据Runtime.availableProcessors方法获取

IO密集型:CPU数量 * CPU利用率 *(1 + 线程等待时间/线程CPU时间)

混合型 => 将任务分为CPU密集型和IO密集型,然后分别使用不同的线程池去处理,从而使每个线程池可以根据各自的工作负载来调整

阻塞队列 => 推荐使用有界队列,有界队列有助于避免资源耗尽的情况发生

拒绝策略 => 默认采用的是AbortPolicy拒绝策略,直接在程序中抛出RejectedExecutionException异常【因为是运行时异常,不强制catch】,这种处理方式不够优雅。处理拒绝策略有以下几种比较推荐:

  • 在程序中捕获RejectedExecutionException异常,在捕获异常中对任务进行处理。针对默认拒绝策略
  • 使用CallerRunsPolicy拒绝策略,该策略会将任务交给调用execute的线程执行【一般为主线程】,此时主线程将在一段时间内不能提交任何任务,从而使工作线程处理正在执行的任务。此时提交的线程将被保存在TCP队列中,TCP队列满将会影响客户端,这是一种平缓的性能降低
  • 自定义拒绝策略,只需要实现RejectedExecutionHandler接口即可
  • 如果任务不是特别重要,使用DiscardPolicy和DiscardOldestPolicy拒绝策略将任务丢弃也是可以的

如果使用Executors的静态方法创建ThreadPoolExecutor对象,可以通过使用Semaphore对任务的执行进行限流也可以避免出现OOM异常。

为什么尽量不要使用Executors创建线程池的更多相关文章

  1. 为什么阿里巴巴要禁用Executors创建线程池?

    作者:何甜甜在吗 juejin.im/post/5dc41c165188257bad4d9e69 看阿里巴巴开发手册并发编程这块有一条:线程池不允许使用Executors去创建,而是通过ThreadP ...

  2. [转]为什么阿里巴巴要禁用Executors创建线程池?

    作者:何甜甜在吗 链接:https://juejin.im/post/5dc41c165188257bad4d9e69 来源:掘金 看阿里巴巴开发手册并发编程这块有一条:线程池不允许使用Executo ...

  3. 阿里不允许使用 Executors 创建线程池!那怎么使用,怎么监控?

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 五常大米好吃! 哈哈哈,是不你总买五常大米,其实五常和榆树是挨着的,榆树大米也好吃, ...

  4. Executors创建线程池的几种方式以及使用

    Java通过Executors提供四种线程池,分别为:   1.newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程.   ...

  5. 【搞定面试官】你还在用Executors来创建线程池?会有什么问题呢?

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

  6. Java 并发:Executors 和线程池

    让我们开始来从入门了解一下 Java 的并发编程. 本文主要介绍如何开始创建线程以及管理线程池,在 Java 语言中,一个最简单的线程如下代码所示: Runnable runnable = new R ...

  7. javade多任务处理之Executors框架(线程池)实现的内置几种方式与两种基本自定义方式

    一 Executors框架(线程池) 主要是解决开发人员进行线程的有效控制,原理可以看jdk源码,主要是由java.uitl.concurrent.ThreadPoolExecutor类实现的,这里只 ...

  8. java线程池之一:创建线程池的方法

    在Java开发过程中经常需要用到线程,为了减少资源的开销,提高系统性能,Java提供了线程池,即事先创建好线程,如果需要使用从池中取即可,Java中创建线程池有以下的方式, 1.使用ThreadPoo ...

  9. 使用Executor框架创建线程池

    Executor框架 Executor类:在java.util.concurrent类中,是JDK并发包的核心类. ThreadPoolExecutor: 线程池. Excutors: 线程池工厂,通 ...

随机推荐

  1. 17.python自定义模块的导入方式

    1.直接用import导入 最后运行main.py可以看到命令行窗口输出了一句:你好,这样就完成了. 2.通过sys模块导入自定义模块的路径path 3.在环境变量中找到自定义模块 这个方法原理就是利 ...

  2. 【一起学源码-微服务】Feign 源码二:Feign动态代理构造过程

    前言 前情回顾 上一讲主要看了@EnableFeignClients中的registerBeanDefinitions()方法,这里面主要是 将EnableFeignClients注解对应的配置属性注 ...

  3. 【转】Java集合框架面试问题集锦

    Java集合框架(例如基本的数据结构)里包含了最常见的Java常见面试问题.很好地理解集合框架,可以帮助你理解和利用Java的一些高级特性.下面是面试Java核心技术的一些很实用的问题. Q:最常见的 ...

  4. ABA问题怎么解:AtomicStampedReference和AtomicMarkableReference

    本博客系列是学习并发编程过程中的记录总结.由于文章比较多,写的时间也比较散,所以我整理了个目录贴(传送门),方便查阅. 并发编程系列博客传送门 并发编程的基石--CAS机制这篇文章中介绍到CAS机制有 ...

  5. docker制作cenos+php56+nginx镜像

    首先你环境要安装好docker 1 获取centos镜像. docker search centos 选取第一个官方镜像. docker pull  docker.io/centos 新建镜像挂载目录 ...

  6. SEATA 分布式事务入门DEMO

    Simple Extensible Autonomous Transacation Architecture,seata是简单的.可扩展.自主性高的分布式架构 SEATA Server Configu ...

  7. Apache Hudi使用问题汇总(一)

    1.如何写入Hudi数据集 通常,你会从源获取部分更新/插入,然后对Hudi数据集执行写入操作.如果从其他标准来源(如Kafka或tailf DFS)中提取数据,那么DeltaStreamer将会非常 ...

  8. 异数OS TCP协议栈测试(三)--长连接篇

    异数OS TCP协议栈测试(三)--长连接篇 本文来自异数OS社区 github:   异数OS-织梦师(消息中间件)群: 476260389 异数OS TCP长连接技术简介 说起长连接,则首先要谈对 ...

  9. equals()和hashCode()必须同时覆盖的原因

    我们在用JAVA创建自己的类的时候,一种比较常见的覆盖就是覆盖Object中的equals()方法和hashCode()方法.如果不这样做的话,就很可能违反Object.hashCode()的通用约定 ...

  10. IDEA工具java开发之 代码生成Generate

    ◆生成set/get ◆生成构造函数 ◆生成toString ◆生成hashCodes和equals