面试突击32:为什么创建线程池一定要用ThreadPoolExecutor?

Python微信订餐小程序课程视频

https://edu.csdn.net/course/detail/36074

Python实战量化交易理财系统

https://edu.csdn.net/course/detail/35475
在 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 list = new ArrayList<>();// 添加任务for (int i = 0; i < 10; i++) {int finalI = i;threadPool.execute(new Runnable() {@Overridepublic 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(设置此值主要是为了方便演示),如下图所示:
![image.png](https://img-blog.csdnimg.cn/img_convert/efdd083c363a9b9c41ee5363b857c309.png#clientId=ua864364e-f98a-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=683&id=ueb99cd9c&margin=[object Object]&name=image.png&originHeight=1365&originWidth=2150&originalType=binary&ratio=1&rotation=0&showTitle=false&size=198199&status=done&style=none&taskId=ueeb2e01e-0a7f-4b57-a4fb-c19fd8e47eb&title=&width=1075)
以上程序的执行结果如下图所示:
![image.png](https://img-blog.csdnimg.cn/img_convert/241d582f9175a1fe751befb3e7be71fa.png#clientId=ud56411a7-ab24-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=573&id=ud7b6df8e&margin=[object Object]&name=image.png&originHeight=1145&originWidth=2924&originalType=binary&ratio=1&rotation=0&showTitle=false&size=294569&status=done&style=none&taskId=ub5f0a310-0317-4cbc-86e7-daec5dd030c&title=&width=1462)
从上述结果可以看出,当线程执行了 7 次之后就开始出现 OutOfMemoryError 内存溢出的异常了。

内存溢出原因分析

想要了解内存溢出的原因,我们需要查看 CachedThreadPool 实现的细节,它的源码如下图所示:
![image.png](https://img-blog.csdnimg.cn/img_convert/f5d6b1a0f01cb3d8cc3c5affb0782626.png#clientId=ua864364e-f98a-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=199&id=u5c9c7355&margin=[object Object]&name=image.png&originHeight=397&originWidth=1806&originalType=binary&ratio=1&rotation=0&showTitle=false&size=83232&status=done&style=none&taskId=uc90443de-70e3-4873-a054-3380de696d6&title=&width=903)
构造函数的第 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 list = new ArrayList<>();// 添加任务for (int i = 0; i < 10; i++) {int finalI = i;threadPool.execute(new Runnable() {@Overridepublic 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("线程池中还有任务在处理");}}
}

以上程序的执行结果如下图所示:
![image.png](https://img-blog.csdnimg.cn/img_convert/4883cd229e15f09ea3dcc25d23a4e33a.png#clientId=ua864364e-f98a-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=260&id=ude5706f1&margin=[object Object]&name=image.png&originHeight=520&originWidth=1996&originalType=binary&ratio=1&rotation=0&showTitle=false&size=70067&status=done&style=none&taskId=u49f0d4bc-b1f0-49a8-8b4e-706075c0c4f&title=&width=998)
从上述结果可以看出,线程池从开始执行到执行结束都没有出现 OOM 的异常,这就是手动创建线程池的优势。

其他创建线程池的问题

除了 CachedThreadPool 线程池之外,其他使用 Executors 自动创建线程池的方式,也存在着其他一些问题,比如 FixedThreadPool 它的实现源码如下:
![image.png](https://img-blog.csdnimg.cn/img_convert/741be157ff698dd9564d53a566197cae.png#clientId=ua864364e-f98a-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=164&id=u0796b9b1&margin=[object Object]&name=image.png&originHeight=327&originWidth=1830&originalType=binary&ratio=1&rotation=0&showTitle=false&size=70565&status=done&style=none&taskId=ubf50d492-479e-4d94-9fb4-b3cb470e0ad&title=&width=915)
而默认情况下任务队列 LinkedBlockingQueue 的存储容量是 Integer.MAX_VALUE,也是趋向于无限大,如下图所示:
![image.png](https://img-blog.csdnimg.cn/img_convert/8ce7ad1e0242f0852bdb65dd6386e184.png#clientId=ua864364e-f98a-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=236&id=u43e680d2&margin=[object Object]&name=image.png&originHeight=472&originWidth=1911&originalType=binary&ratio=1&rotation=0&showTitle=false&size=78162&status=done&style=none&taskId=u0d4d184f-3189-4085-860a-5b0f559d3d8&title=&width=955.5)
这样就也会造成,因为线程池的任务过多而导致的内存溢出问题。其他几个使用 Executors 自动创建线程池的方式也存在此问题,这里就不一一演示了。

总结

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

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

公众号:Java面试真题解析

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/401212.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Bootstrap datepicker 在弹出窗体modal中不工作

解决办法 在 show 方法后面 添加 下面一段代码 $(#modalCard).modal(show);—例子 打开 弹出窗体 //$(#modalCard).modal(hide); $(#modalCard).on(shown.bs.modal, function () { //$(.input-group.date).datetimepicker({ $(#dpReceiveDate).datetimepicker({ format: "…

学习Samba基础命令详解之大话西游01

服务名:smb配置目录:/etc/sabma/主配置文件:/etc/sabma/smb.conf# Global Settings 17行workgroup语法 workgtoup <工作组群>; 预设 workgroup MYGROUP 说明 设定 Samba Server 的工作组 例 workgroup workgroup 和WIN2000S设为一个组&#xff0c;可在网上邻居可中看到…

实例讲解getopt()函数的使用

[cpp] view plaincopy #include <stdio.h> #include <unistd.h> int main(int argc, char *argv[]) { extern char *optarg;//保存选项的参数 extern int optind, opterr, optopt; int ch; printf("\n\n"); pri…

机器学习实战 | SKLearn最全应用指南

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 作者&#xff1a;韩信子ShowMeAI 教程地址&#xff1a;http://www.showmeai.tech/tutorials/41 本文地址&#xff1a;http…

windows 64位 安装mvn提示 不是内部或外部命令

在安装mvn的过程中当在mvn的目录下去执行mvn命令的时候是可以正常执行的&#xff0c;当设置好环境变量后执行后发现提示mvn不是内部命令。 原因是设置的MAVEN_HOME变量未被Path解析&#xff0c;解决办法是 直接把path中的%MAVEN_HOME%\bin 换成MAVEN_HOME的绝对路径&#xff0c…

Scheme语言入门

2019独角兽企业重金招聘Python工程师标准>>> Scheme语言入门 最早听说 LISP&#xff0c;是 Stallman 的 GNU Emacs 中将 LISP 作为嵌入语言&#xff0c;定制和增强 Emacs。GNU Emacs 是一个文本编辑器&#xff0c;文本就是一种符号&#xff0c;而 Lisp 正好就是针对…

题目四 艺术品

Dr.Kong设计了一件艺术品&#xff0c;该艺术品由N个构件堆叠而成&#xff0c;N个构件从高到低按层编号依次为1&#xff0c;2&#xff0c;……,N。艺术品展出后&#xff0c;引起了强烈的反映。Dr.Kong观察到&#xff0c;人们尤其对作品的高端部分评价甚多。 狂热的Dr.Kong一激动…

如何将docker 镜像上传到docker hub仓库

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 如何将docker 镜像上传到docker hub仓库 目录* 如何将docker 镜像上传到docker hub仓库 背景 1.注册docker hub账号 2.…

C# 类(14) 事件

using System; using System.Collections.Generic; using System.Linq; using System.Text;namespace ConsoleApplication1 {//先在外面定义一个类.class MyClass{//委托是事件的前提,所以先定义一个委托public delegate void Mydelagate(int i);// 接着定义事件. public event…

ThinkPHP框架 _ 学习3

【路由解析】 通过url地址get参数找到指定的控制器&#xff0c;并进行对应方法调用请求 http://网址/index.php?m模块名称&c控制器&a方法 以上url地址信息代码不够优雅、不安全。 tp框架url地址可以由以下四种 http://网址/index.php?mXX&cXX&aXX 基本get模…

The slave I/O thread stops(equal MySQL server ids)

在学习replication时遇到了如下问题&#xff1a;显然看到Slave_IO_Running 为NO 表示有问题&#xff1b;到日志里查看&#xff0c;错误如下&#xff1a;position 98100121 17:09:03 [ERROR] The slave I/O thread stops because master and slave have equal MySQL server ids;…

pytest配置文件pytest.ini

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 说明&#xff1a; pytest.ini是pytest的全局配置文件&#xff0c;一般放在项目的根目录下是一个固定的文件-pytest.ini可以…

函数声明、引用

1.函数的声明 function 函数名(形参1&#xff0c;形参2&#xff0c;形参3....){ 函数体&#xff1b; return; } 2.函数的调用方式&#xff1a; func(1,2,3); 函数名(参数1&#xff0c;参数2&#xff0c;参数3); 3.return:&#xff0c;并返回一个函…

基于积分墙盈利模式的APP架构思考

基于积分墙盈利模式的APP架构思考from: http://kuailiyu.cyzone.cn/article/4156.html个人感言&#xff1a;一款小游戏好不容易辛辛苦苦开发出来&#xff0c;但是在后期如何不注重推荐&#xff0c;其下场可想而知。而个人游戏开发者的产品很难实现应用内付费集成&#xff0c;技…

flash中的渐变滤镜GradientGlowFilter

可使用 GradientGlowFilter 类对显示对象应用渐变发光效果。 渐变发光是一种非常逼真的发光效果&#xff0c;您可以控制颜色渐变。 可以在对象的内缘或外缘的周围或者对象的顶部应用渐变发光。 您可以将滤镜应用于任何显示对象&#xff08;即&#xff0c;从 DisplayObject 类继…

【死磕NIO】— 探索 SocketChannel 的核心原理

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 大家好&#xff0c;我是大明哥&#xff0c;一个专注于【死磕 Java】系列创作的程序员。 【死磕 Java 】系列为作者「chenssy…

RSync实现文件备份同步

转自&#xff1a;http://www.mike.org.cn/blog/index.php?loadread&id639###pp0 [rsync实现网站的备份&#xff0c;文件的同步&#xff0c;不同系统的文件的同步&#xff0c;如果是windows的话&#xff0c;需要windows版本cwrsync] 一、什么是rsync rsync&#xff0c;remo…

session的存储方式

1、保存在IIS进程中 2、保存在StateServer上 3、保存在SQL Server数据库中 转载于:https://www.cnblogs.com/dashi/archive/2012/10/10/4034799.html

PixiJS - 基于 WebGL 的超快 HTML5 2D 渲染引擎

Pixi.js 是一个开源的HTML5 2D 渲染引擎&#xff0c;使用 WebGL 实现&#xff0c;不支持的浏览器会自动降低到 Canvas 实现。PixiJS 的目标是提供一个快速且轻量级的2D库&#xff0c;并能兼容所有设备。此外&#xff0c;让开发者无需了解WebGL&#xff0c;就可以感受到硬件加速…

腾讯的老照片修复算法,我把它搬到网上,随便玩

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 大家好&#xff0c;之前向大家介绍并跑通了腾讯开源的老照片修复算法&#xff08;AI 黑科技&#xff0c;老照片修复&#xf…