Java创建线程池为什么一定要用ThreadPoolExecutor
作者:??Java中文社群???? 发布时间:2023-04-22 06:03:31
前言:
在 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 自动创建线程池的方式也存在此问题,这里就不一一演示了。
来源:https://juejin.cn/post/7077358563764469796
猜你喜欢
- 一、静态代理模式1.1、 代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标
- Spring main方法调用Dao层和Service层的方法在web环境中,一般serviceImpl中的dao之类的数据库连接都由容器启
- 这篇文章主要介绍了Spring @Transactional注解失效解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的
- 缓存淘汰算法在高并发、高性能的质量要求不断提高时,我们首先会想到的就是利用缓存予以应对。第一次请求时把计算好的结果存放在缓存中,下次遇到同样
- maven 打包 动态启动脚本介绍如何通过maven的环境变量动态打包, 并动态改变启动脚本中的环境参数之前都是每个环境一个启动脚本, 其实
- startActivityForResult与startActivity的不同之处在于:1、startActivity( )仅仅是跳转到目标
- 前言SpringBoot是我们经常使用的框架,那么你能不能针对SpringBoot实现自动配置做一个详细的介绍。如果可以的话,能不能画一下实
- 本文实例为大家分享了java连接SQL Server数据库的具体代码,供大家参考,具体内容如下操作系统:windows 10 64位java
- 前言:页面静态化其实就是将原来的 * 页(例如通过ajax请求动态获取数据库中的数据并展示的网页)改为通过静态化技术生成的静态网页,这样用户
- 使用InsertProvider注解报错解决目前项目在使用mybatis,并且是使用注解的方式。在使用InsertProvider注解的时候
- 方案一: 采用reflections 框架(此框架依赖com.google.guava)1、reflections框架地址:https://
- 简述:JRebel是一款JVM插件,它使得Java代码修改后不用重启系统,立即生效。IDEA上原生是不支持热部署的,一般更新了 Java 文
- 死锁在多线程的情况下,会出现数据不同步情况, 而为了避免这种情况,之前也说了:界区实现方法有两种,一种是用synchronized,一种是用
- 对象内存分配与回收策略对象的内存分配,往大方向讲,就是在堆上分配〔但也可能经过JIT编译后被拆散为标量类型并间接地栈上分配),对象主要分配在
- 本文实例为大家分享了C语言实现贪吃蛇游戏的具体代码,供大家参考,具体内容如下IDE用的是 VS2019先看效果 代码全览game.
- ? 通配符类型<? extends T> 表示类型的上界,表示参数化类型的可能是T 或是 T的子类;<? super T&
- 继承ClassLoader并且重写findClass方法就可以自定义一个类加载器,具体什么是类加载器以及类加载器的加载过程与顺序下次再说,下
- MyBatis 是一款优秀的持久层框架,被各大互联网公司使用,本文使用Spring Boot整合Mybatis,并完成CRUD操作。为什么要
- 本文以实例阐述了C++中形参与实参的区别,有助于读者加深对于C++形参与实参的认识。形参出现在函数定义中,在整个函数体内都可以使用, 离开该
- java获取系统路径字体、得到某个目录下的所有文件名、获取当前路径package com.liuxing.test;import java.