在 Java 语言中,并发编程都是依靠线程池完成的,而线程池的创建方式又有很多,但从大的分类来说,线程池的创建总共分为两大类:手动方式使用 ThreadPoolExecutor 创建线程池和使用 Executors 执行器自动创建线程池。

那究竟要使用哪种方式来创建线程池呢?我们今天就来详细的聊一聊。

先说结论

在 Java 语言中,一定要使用 ThreadPoolExecutor 手动的方式来创建线程池,因为这种方式可以通过参数来控制最大任务数和拒绝策略,让线程池的执行更加透明和可控,并且可以规避资源耗尽的风险。

OOM风险演示

假如我们使用了 Executors 执行器自动创建线程池的方式来创建线程池,那么就会存现线程溢出的风险,以 CachedThreadPool 为例我们来演示一下:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; public class ThreadPoolExecutorExample {
static class OOMClass {
// 创建 1MB 大小的变量(1M = 1024KB = 1024*1024Byte)
private byte[] data_byte = new byte[1 * 1024 * 1024];
}
public static void main(String[] args) throws InterruptedException {
// 使用执行器自动创建线程池
ExecutorService threadPool = Executors.newCachedThreadPool();
List<Object> list = new ArrayList<>();
// 添加任务
for (int i = 0; i < 10; i++) {
int finalI = i;
threadPool.execute(new Runnable() {
@Override
public void run() {
// 定时添加
try {
Thread.sleep(finalI * 200);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 将 1M 对象添加到集合
OOMClass oomClass = new OOMClass();
list.add(oomClass);
System.out.println("执行任务:" + finalI);
}
});
}
}
}

第 2 步将 Idea 中 JVM 最大运行内存设置为 10M(设置此值主要是为了方便演示),如下图所示:



以上程序的执行结果如下图所示:



从上述结果可以看出,当线程执行了 7 次之后就开始出现 OutOfMemoryError 内存溢出的异常了。

内存溢出原因分析

想要了解内存溢出的原因,我们需要查看 CachedThreadPool 实现的细节,它的源码如下图所示:



构造函数的第 2 个参数被设置成了 Integer.MAX_VALUE,这个参数的含义是最大线程数,所以由于 CachedThreadPool 并不限制线程的数量,当任务数量特别多的时候,就会创建非常多的线程。而上面的 OOM 示例,每个线程至少要消耗 1M 大小的内存,加上 JDK 系统类的加载也要占用一部分的内存,所以当总的运行内存大于 10M 的时候,就出现内存溢出的问题了。

使用ThreadPoolExecutor来改进

接下来我们使用 ThreadPoolExecutor 来改进一下 OOM 的问题,我们使用 ThreadPoolExecutor 手动创建线程池的方式,创建一个最大线程数为 2,最多可存储 2 个任务的线程池,并且设置线程池的拒绝策略为忽略新任务,这样就能保证线程池的运行内存大小不会超过 10M 了,实现代码如下:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*; /**
* ThreadPoolExecutor 演示示例
*/
public class ThreadPoolExecutorExample {
static class OOMClass {
// 创建 1MB 大小的变量(1M = 1024KB = 1024*1024Byte)
private byte[] data_byte = new byte[1 * 1024 * 1024];
} public static void main(String[] args) throws InterruptedException {
// 手动创建线程池,最大线程数 2,最多存储 2 个任务,其他任务会被忽略
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 2,
0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2),
new ThreadPoolExecutor.DiscardPolicy()); // 拒绝策略:忽略任务
List<Object> list = new ArrayList<>();
// 添加任务
for (int i = 0; i < 10; i++) {
int finalI = i;
threadPool.execute(new Runnable() {
@Override
public void run() {
// 定时添加
try {
Thread.sleep(finalI * 200);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 将 1m 对象添加到集合
OOMClass oomClass = new OOMClass();
list.add(oomClass);
System.out.println("执行任务:" + finalI);
}
});
}
// 关闭线程池
threadPool.shutdown();
// 检测线程池的任务执行完
while (!threadPool.awaitTermination(3, TimeUnit.SECONDS)) {
System.out.println("线程池中还有任务在处理");
}
}
}

以上程序的执行结果如下图所示:



从上述结果可以看出,线程池从开始执行到执行结束都没有出现 OOM 的异常,这就是手动创建线程池的优势。

其他创建线程池的问题

除了 CachedThreadPool 线程池之外,其他使用 Executors 自动创建线程池的方式,也存在着其他一些问题,比如 FixedThreadPool 它的实现源码如下:



而默认情况下任务队列 LinkedBlockingQueue 的存储容量是 Integer.MAX_VALUE,也是趋向于无限大,如下图所示:



这样就也会造成,因为线程池的任务过多而导致的内存溢出问题。其他几个使用 Executors 自动创建线程池的方式也存在此问题,这里就不一一演示了。

总结

线程池的创建方式总共分为两大类:手动使用 ThreadPoolExecutor 创建线程池和自动使用 Executors 执行器创建线程池的方式。其中使用 Executors 自动创建线程的方式,因为线程个数或者任务个数不可控,可能会导致内存溢出的风险,所以在创建线程池时,建议使用 ThreadPoolExecutor 的方式来创建。

是非审之于己,毁誉听之于人,得失安之于数。

公众号:Java面试真题解析

面试合集:https://gitee.com/mydb/interview

面试突击32:为什么创建线程池一定要用ThreadPoolExecutor?的更多相关文章

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

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

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

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

  3. Flash图解线程池 | 阿里巴巴面试官希望问的线程池到底是什么?

    前言 前几天小强去阿里巴巴面试Java岗,止步于二面. 他和我诉苦自己被虐的多惨多惨,特别是深挖线程和线程池的时候,居然被问到不知道如何作答. 对于他的遭遇,结合他过了一面的那个嘚瑟样,我深表同情(加 ...

  4. Python并发编程之消息队列补充及如何创建线程池(六)

    大家好,并发编程 进入第六篇. 在第四章,讲消息通信时,我们学到了Queue消息队列的一些基本使用.昨天我在准备如何创建线程池这一章节的时候,发现对Queue消息队列的讲解有一些遗漏的知识点,而这些知 ...

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

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

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

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

  7. 你创建线程池最好分为两种线程池,io密集型线程池,或者cpu密集型线程池

    你创建线程池最好分为两种线程池,io密集型线程池,或者cpu密集型线程池. 否则,如果只用一个线程池的话,不管是iO密集的线程,或者cpu消耗大的都放在同一个线程池的话,会发生线程池被撑满的情况

  8. 使用Runnable接口创建线程池

    步骤: 创建线程池对象创建 Runnable 接口子类对象提交 Runnable 接口子类对象关闭线程池实例: class TaskRunnable implements Runnable{ @Ove ...

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

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

随机推荐

  1. WebGPU 中的缓冲映射机制

    1. 什么是缓冲映射 就不给定义了,直接简单的说,映射(Mapping)后的某块显存,就能被 CPU 访问. 三大图形 API(D3D12.Vulkan.Metal)的 Buffer(指显存)映射后, ...

  2. Kubernetes 集群和应用监控方案的设计与实践

    目录 Kubernetes 监控 监控对象 Prometheus 指标 实践 节点监控 部署 Prometheus 部署 Kube State Metrics 部署 Grafana 应用如何接入 Pr ...

  3. 卡特兰数是我见过第二神奇的东西//下一个是stirling数列

    自从上次斐波那契的总结后,今天有一次遇上了正宗卡特兰数. 1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786, 208012, 742900, ...

  4. 同事提出个我从未想过的问题,为什么Kubernetes要"多此一举"推出静态Pod概念?

    同事提出个我从未想过的问题,为什么Kubernetes要"多此一举"推出静态Pod概念? 我们知道k8s中Pod可以说是一个合格的容器小管家,Pod 被设计成支持多个容器可以一起进 ...

  5. opencv笔记---contours

    一 Contour Finding Contours使用 STL-style vector<> 表示,如 vector<cv::Point>, vector<cv::Po ...

  6. 前端表单标签form 及 简单应用

    今日内容 form 表单(重点) 后端框架之 flask 简介 内容详细 form 表单 1.作用 form 表单可以在前端获取用户输入的数据并发送给后端(服务端) 2.input 标签 获取用户数据 ...

  7. Solution -「CF 1025D」Recovering BST

    \(\mathcal{Description}\)   Link.   给定序列 \(\{a_n\}\),问是否存在一棵二叉搜索树,使得其中序遍历为 \(\{a_n\}\),且相邻接的两点不互素.   ...

  8. java中的代码块(初始化块)

    介绍 代码块又叫初始化块,属于类中的成员,类似于方法,将逻辑语句封装在方法体中,通过{ } 包围起来,但和方法不同,没有方法名,没有返回值,没有参数,只有方法体,而且不通过对象或类显示调用,而是加载类 ...

  9. 3D 世界的钥匙「GitHub 热点速览 v.22.08」

    有没有想过把身边的物件儿转成 3D 动画,在网页上实现一把?本期特推的项目 Three.js 就是帮你创建 3D 页面的知名开源项目,好玩的 3D 世界在向你招手.除了打开浏览器 3D 世界的钥匙外, ...

  10. Dubbo基础之四管理控制台 dubbo-admin

    Dubbo提供一个重要功能就是服务治理(SOA governance),什么是服务治理呢?企业为了确保项目顺利完成而实施的过程,需要进行各方面的管理.服务治理就是用来管理SOA的采用和实现的过程. 服 ...