Java中线程池,你真的会用吗?

在《深入源码分析Java线程池的实现原理》这篇文章中,我们介绍过了Java中线程池的常见用法以及基本原理。

在文中有这样一段描述:

可以通过Executors静态工厂构建线程池,但一般不建议这样使用。

关于这个问题,在那篇文章中并没有深入的展开。作者之所以这么说,是因为这种创建线程池的方式有很大的隐患,稍有不慎就有可能导致线上故障,如:一次Java线程池误用引发的血案和总结( https://zhuanlan.zhihu.com/p/32867181 )

本文我们就来围绕这个问题来分析一下为什么JDK自身提供的构建线程池的方式并不建议使用?到底应该如何创建一个线程池呢?

Executors

Executors 是一个Java中的工具类。提供工厂方法来创建不同类型的线程池。

从上图中也可以看出,Executors的创建线程池的方法,创建出来的线程池都实现了ExecutorService接口。常用方法有以下几个:

newFiexedThreadPool(int Threads):创建固定数目线程的线程池。

newCachedThreadPool():创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果没有可用的线程,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。

newSingleThreadExecutor()创建一个单线程化的Executor。

newScheduledThreadPool(int corePoolSize)创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。

类看起来功能还是比较强大的,又用到了工厂模式、又有比较强的扩展性,重要的是用起来还比较方便,如:

ExecutorService executor = Executors.newFixedThreadPool(nThreads) ;

即可创建一个固定大小的线程池。

但是为什么我说不建议大家使用这个类来创建线程池呢?

我提到的是『不建议』,但是在阿里巴巴Java开发手册中也明确指出,而且用的词是『不允许』使用Executors创建线程池。

Executors存在什么问题

在阿里巴巴Java开发手册中提到,使用Executors创建线程池可能会导致OOM(OutOfMemory ,内存溢出),但是并没有说明为什么,那么接下来我们就来看一下到底为什么不允许使用Executors?

我们先来一个简单的例子,模拟一下使用Executors导致OOM的情况。

/*** @author Hollis*/
public class ExecutorsDemo { 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 java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416) at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371) at com.hollis.ExecutorsDemo.main(ExecutorsDemo.java:16)

以上代码指出,ExecutorsDemo.java的第16行,就是代码中的executor.execute(new SubThread());

Executors为什么存在缺陷

通过上面的例子,我们知道了Executors创建的线程池存在OOM的风险,那么到底是什么原因导致的呢?我们需要深入Executors的源码来分析一下。

其实,在上面的报错信息中,我们是可以看出蛛丝马迹的,在以上的代码中其实已经说了,真正的导致OOM的其实是LinkedBlockingQueue.offer方法。

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416) at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371) at com.hollis.ExecutorsDemo.main(ExecutorsDemo.java:16)

如果读者翻看代码的话,也可以发现,其实底层确实是通过LinkedBlockingQueue实现的:

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

如果读者对Java中的阻塞队列有所了解的话,看到这里或许就能够明白原因了。

Java中的BlockingQueue主要有两种实现,分别是ArrayBlockingQueue 和 LinkedBlockingQueue

ArrayBlockingQueue是一个用数组实现的有界阻塞队列,必须设置容量。

LinkedBlockingQueue是一个用链表实现的有界阻塞队列,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE

这里的问题就出在:不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE。也就是说,如果我们不设置LinkedBlockingQueue的容量的话,其默认容量将会是Integer.MAX_VALUE

newFixedThreadPool中创建LinkedBlockingQueue时,并未指定容量。此时,LinkedBlockingQueue就是一个无边界队列,对于一个无边界队列来说,是可以不断的向队列中加入任务的,这种情况下就有可能因为任务过多而导致内存溢出问题。

上面提到的问题主要体现在newFixedThreadPoolnewSingleThreadExecutor两个工厂方法上,并不是说newCachedThreadPoolnewScheduledThreadPool这两个方法就安全了,这两种方式创建的最大线程数可能是Integer.MAX_VALUE,而创建这么多线程,必然就有可能导致OOM。

创建线程池的正确姿势

避免使用Executors创建线程池,主要是避免使用其中的默认实现,那么我们可以自己直接调用ThreadPoolExecutor的构造函数来自己创建线程池。在创建的同时,给BlockQueue指定容量就可以了。

private static ExecutorService executor = new ThreadPoolExecutor(10, 10, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue(10));

这种情况下,一旦提交的线程数超过当前可用线程数时,就会抛出java.util.concurrent.RejectedExecutionException,这是因为当前线程池使用的队列是有边界队列,队列已经满了便无法继续处理新的请求。但是异常(Exception)总比发生错误(Error)要好。

除了自己定义ThreadPoolExecutor外。还有其他方法。这个时候第一时间就应该想到开源类库,如apache和guava等。

作者推荐使用guava提供的ThreadFactoryBuilder来创建线程池。

public class ExecutorsDemo { private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder() .setNameFormat("demo-pool-%d").build(); private static ExecutorService pool = new ThreadPoolExecutor(5, 200, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy()); public static void main(String[] args) { for (int i = 0; i < Integer.MAX_VALUE; i++) { pool.execute(new SubThread()); } } }

通过上述方式创建线程时,不仅可以避免OOM的问题,还可以自定义线程名称,更加方便的出错的时候溯源。

思考题,文中作者说:发生异常(Exception)要比发生错误(Error)好,为什么这么说?

文中提到的《阿里巴巴Java开发手册》,请关注公众号Hollis,回复:手册。即可获得完整版PDF。

 

from: https://www.hollischuang.com/archives/2888

转载于:https://www.cnblogs.com/GarfieldEr007/p/9954877.html

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

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

相关文章

java (lodop) 打印实例

首先在lodop官网下载相关文件&#xff08;js、css等&#xff09;&#xff1a;http://www.lodop.net/download.html 在下载好的包里 除了html页面 其他的js、css等拷贝到项目的一个目录下、新建个lodop文件夹。 lodop主要接口函数如下&#xff1a; ● PRINT_INIT(strPrintTaskN…

深入了解java虚拟机(JVM) 第四章 对象的创建

一、对象的创建过程 对象的创建过程大致可以分为六步&#xff0c;其中对象的分配尤为重要&#xff1a; 二、对象分配内存 一般来说对象分配内存有两种方式&#xff1a; 第一种是指针碰撞&#xff0c;这是一种比较理想的方式&#xff1a;如果Java堆是绝对规整的&#xff1a;一边…

LODOP使用问题解决汇总

LODOP 打印控件出现问题及修改方法 问题1 、打印网页时页面出现电脑设置的底色如何解决&#xff1f; 解决方法 &#xff1a;按照如下方式添加HTML页面 var strHTML"<body stylemargin:0;background-color: white>"document.getElementById("table02&qu…

[UWP]使用Picker实现一个简单的ColorPicker弹窗

[UWP]使用Picker实现一个简单的ColorPicker弹窗 原文:[UWP]使用Picker实现一个简单的ColorPicker弹窗在上一篇博文《[UWP]使用Popup构建UWP Picker》中我们简单讲述了一下使用Popup构建适用于MVVM框架下的弹窗层组件Picker的过程。但是没有应用实例的话可能体现不出Picker相对于…

java sort排序

问题&#xff1a;对list中的对象中的属性值排序&#xff1a; User对象&#xff1a; public class User {private int id;private String Name; public int getid() {return id;}public void setid(int id) {this.id id;}public String getName() {return Name;}pub…

C# WebRequest.Create 锚点“#”字符问题

背景 在与后台API接口对接时&#xff0c;如将网页Url作为参数请求数据时&#xff0c;如果是锚点参数&#xff0c;则会丢失。 锚点参数 请求通过WebRequest.Create创建一个WebRequest&#xff1a; 1 var uri "https://id.test.xxx.com/api/v1/auth/sso/url?redirectUrlht…

Postgresql 按30分钟、小时、天分组

按30分钟统计&#xff1a; shool_time格式为varchar&#xff0c; 例如 201911050808、201911050820、201911050842 分组后结果&#xff1a; 20191105 0800 20191105 0830 20191105 0900 20191105 0930 注&#xff1a;小于30分钟的按00统计&#xff0c;大于30分钟的按30统计 SEL…

Java calendar加减时间

//时间格式可以改成自己想要的格式&#xff0c;例yyyy-MM-dd HH:mm等等。 private final static String formatyyyyMMddHHMM "yyyyMMddHHmm";String currTime "201911051500";SimpleDateFormat yearFormat new SimpleDateFormat(formatyyyyMMddHHMM); D…

免费的编程中文书籍索引(2018第三版)

之前我在 github 上整理了来一份&#xff1a;free-programming-books-zh_CN&#xff08;免费的计算机编程类中文书籍&#xff09;。 截至目前为止&#xff0c;已经在 GitHub 收获了 40000 多的 stars&#xff0c;有 90 多人发了 600 多个 Pull Requests 和 issues。 在收集的过…

java中Map ListE的用法

返回的restful接口数据样式&#xff1a; {party: [{"finishTime": "20191105","busiCnt": 1000}]}1、定义WzwScreenEntity 实体 public class WzwScreenEntity {private String finishTime;private String busiCnt;private String party;publi…

ORM是什么?如何理解ORM?

一、ORM简介 对象关系映射&#xff08;Object Relational Mapping&#xff0c;简称ORM&#xff09;模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。简单的说&#xff0c;ORM是通过使用描述对象和数据库之间映射的元数据&#xff0c;将程序中的对象…

遍历Map keySet和entrySet

public class test {public static void main(String[] args) {Map<String, String> map new HashMap<String, String>();map.put("1", "广东");map.put("2", "广西");map.put("3", "广南");map.put…

falcon适配ldap密码同步

问题 小米的openfalcon在使用ldap首次登陆成功后,会在本地创建同名的账号, 这就有个问题当你更新了ldap的密码时,openfalcon是没有同步本地账号密码的功能 二次改造 方便我们debug, 先把日志的debug打开,默认是没有运行时日志的,只有console日志 # 编辑文件 dashboard/rrd/util…

如何有效的使用 for循环和Iterator遍历

遍历ArrayList 使用for循环遍历的速度会比Iterator遍历的速度要快&#xff1a; for (int i0, nlist.size(); i < n; i)list.get(i);runs faster than this loop:for (Iterator ilist.iterator(); i.hasNext();)i.next();采用ArrayList对随机访问比较快&#xff0c;而for循环…

【js】数组置空的其他方式及使用场景

数组在js中属于引用型类型。 var arr [1, 2, 3]; a []; 通常使用以上方式。如果使用场景必须使用方法置空&#xff0c; 可以采用arr.length 0; 或者使用a.splice(0, a.length); 使用场景 vue2中组定义组件中v-model的值是数组类型&#xff0c; 组件内部需要清空时&#xff0…

Postgresql 填充所有的时间点

最近使用有个业务需要展示20190101和20190106所有的时间段&#xff0c;例如 20190101 20190102 20190103 20190104 20190105 20190106但是数据库中存的只有以下 20190101 20190102 20190104 20190106那我们需要把20190103、20190105填充进去。 方式一&#xff1a; 新建个存储…

分类与监督学习,朴素贝叶斯分类算法

1.理解分类与监督学习、聚类与无监督学习。 &#xff08;1&#xff09;简述分类与聚类的联系与区别。 分类就是按照某种标准给对象贴标签&#xff0c;再根据标签来区分归类。聚类是指事先没有“标签”而通过某种成团分析找出事物之间存在聚集性原因的过程。 区别是&#xff0c…

Postgre合并多行数据为一行

业务情景描述&#xff1a;用户需要一行展示所有的文件名称&#xff0c; 比如&#xff1a;你的1.txt、2.txt、3.txt未上传成功&#xff0c;请核查&#xff01; SELECT * FROM fileTable --查询结果如下&#xff1a; 1.txt 2.txt 3.txt方式一&#xff1a;使用string_agg函数&…

如何学好Spring

要学好Spring&#xff0c;首先要明确Spring是个什么东西&#xff0c;能帮我们做些什么事情&#xff0c;知道了这些然后做个简单的例子&#xff0c;这样就基本知道怎么使用Spring了。Spring核心是IoC容器&#xff0c;所以一定要透彻理解什么是IoC容器&#xff0c;以及如何配置及…

数据库查询字段为空时,返回0

oracle select nvl(字段名,0) from 表名; sqlserver select isnull(字段名,0) from 表名; mysql select ifnull(字段名,0) from 表名; postgresql select coalesce(字段名,0) from 表名;