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

转载自   Java中线程池,你真的会用吗

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

在文中有这样一段描述:

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

关于这个问题,在那篇文章中并没有深入的展开。作者之所以这么说,是因为这种创建线程池的方式有很大的隐患,稍有不慎就有可能导致线上故障。

本文我们就来围绕这个问题来分析一下为什么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 {@Overridepublic 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 exceededat 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 exceededat 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)好,为什么这么说?

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

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

相关文章

解决 CefSharp WPF控件不能使用输入法输入中文的问题(代码已提交到 github)

首先&#xff0c;本文所有 代码已经提交到github&#xff0c;需要的可以直接从github获取&#xff1a;https://github.com/starts2000/CefSharp&#xff0c;希望可以帮助到有需要的朋友们。 CEF 简介 CEF is a BSD-licensed open source project founded by Marshall Greenbl…

ssl1747-登山机器人【离散化,玄学,贪心】

正题 大意 有n个机器人&#xff0c;每个机器人带xi点能量&#xff0c;行走需要消耗yi点能量&#xff0c;求这个n个机器人不回头的接力最多能走多远&#xff08;一个走到就行了&#xff09;。 解题思路 好像不是很难&#xff0c;处理接力时油的交接就好了。 假设现在有两个机…

java中的泛型类型擦除

大家好&#xff0c;我是雄雄&#xff0c;今天给大家分享的是&#xff1a;java的泛型是类型擦除的。写在前面那么何为类型擦除&#xff1f;类型擦除就是在编译期明确去掉所编程序的类型系统。啥&#xff1f;还不明白&#xff1f;以java泛型为例&#xff0c;java的泛型在编译期有…

性能优化之抛弃Calendar

转载自 性能优化之抛弃Calendar 目前在做限流相关的需求&#xff0c;有这么一个限流策略&#xff0c;和用户相关&#xff0c;当系统发生故障时&#xff0c;允许一个非核心接口按照用户的百分比进行限流&#xff0c;如果完全按照UUID进行hash&#xff0c;那么每次都是限制同一…

基于.net的微服务架构下的开发测试环境运维实践

眼下&#xff0c;做互联网应用&#xff0c;最火的架构是微服务&#xff0c;最热的研发管理就是DevOps&#xff0c; 没有之一。微服务、DevOps已经被大量应用&#xff0c;它们已经像传说中的那样&#xff0c;可以无所不能。特来电云平台&#xff0c;通过近两年多的实践&#xff…

洛谷P3845-球赛【离散化,贪心】

正题 题目连接&#xff1a; https://www.luogu.org/problemnew/show/P3845 AC信息&#xff1a; https://www.luogu.org/record/show?rid6955910 大意 有一些比分记录&#xff0c;不过双方可能会搞反&#xff08;如1−21−2可能会记录成2−12−1&#xff09;求这之间至少有…

挺详细的spring+springmvc+mybatis配置整合|含源代码

大家好&#xff0c;我是雄雄&#xff0c;今天来带着大家来配置一下SSM&#xff08;springspringmvcmybatis&#xff09;框架。01新建java web项目直接在myeclipse中&#xff0c;新建一个web项目即可。02导入jar包将SSM所需的jar包复制到项目的/WebRoot/WEB-INF/lib中&#xff0…

Java中如何实现线程的超时中断

转载自 Java中如何实现线程的超时中断 背景 之前在实现熔断降级组件的时候&#xff0c;需要实现接口请求的超时中断。意思是&#xff0c;业务在使用熔断降级功能时&#xff0c;在平台上设置了一个超时时间&#xff0c;如果请求进入熔断器开始计时&#xff0c;接口在超时时间…

GDOI2018-《被虐记》

序章 五一劳动节当然是来劳动的啦&#xff01;于是乎五一就变成一件不存在的事&#xff0c;放学那天的中午就坐车去中山了。 然后睡了一会就快到了&#xff0c;下了高速&#xff0c;突然想起来这次带来手机就照了一张照。然后就到酒店了。附近也有挺多吃的&#xff0c;酒店一…

DDD理论学习系列(10)-- 聚合

1.引言 聚合&#xff0c;最初是UML类图中的概念&#xff0c;表示一种强的关联关系&#xff0c;是一种整体与部分的关系&#xff0c;且部分能够离开整体而独立存在&#xff0c;如车和轮胎。 在DDD中&#xff0c;聚合也可以用来表示整体与部分的关系&#xff0c;但不再强调部分…

#面试!,一定要注意,避免踩这些雷!!

大家好&#xff0c;我是雄雄。前言今天&#xff0c;对2班的同学们进行了模拟面试&#xff0c;由于学生们第一次参与模拟面试&#xff0c;所以会出现各种各样的问题&#xff0c;有应该出的&#xff0c;也有不该出的&#xff0c;现在做个简单的总结&#xff0c;供三班的孩子们参考…

一次动态代理的填坑之旅

转载自 一次动态代理的填坑之旅 背景 想在现有的接口加上熔断降级或者限流的功能&#xff0c;比较好的方式是通过注解的方式&#xff0c;并基于动态代理进行实现&#xff0c;下面代码是Rhino的实现 Rhino public class ServiceImpl {Degrade(rhinoKey "syncMethod-0&…

3班的第一次模拟面试

大家好&#xff0c;我是雄雄&#xff0c;前几天总结了下面试别的班时所出现的问题&#xff0c;今天&#xff0c;3班进行了第一次模拟面试。虽然在面试之前千叮咛万嘱咐&#xff0c;但是在正式模拟面试时还是有些问题。一方面由于这样的模拟面试以前没有过&#xff0c;所以紧张过…

漫画:如何实现大整数相加

转载自 漫画&#xff1a;如何实现大整数相加 在程序中列出的 “竖式” 究竟是什么样子呢&#xff1f;我们以 426709752318 95481253129 为例&#xff0c;来看看大整数相加的详细步骤&#xff1a; 第一步&#xff0c;把整数倒序存储&#xff0c;整数的个位存于数组0下标位置&…

ssl2644-线段树练习1【线段树】

正题 题意 一块长m的墙&#xff0c;有n个大小不同的盒子放在前面&#xff0c;求没有被挡住的墙的总长度 解题思路 用线段树&#xff0c;0表示有没被遮挡的&#xff0c;1表示完全被遮挡&#xff0c;-1表示有遮挡的和没遮挡的。然后记数。 代码 #include<cstdio> usin…

Entity Framework Core 执行SQL语句和存储过程

无论ORM有多么强大&#xff0c;总会出现一些特殊的情况&#xff0c;它无法满足我们的要求。在这篇文章中&#xff0c;我们介绍几种执行SQL的方法。 表结构 在具体内容开始之前&#xff0c;我们先简单说明一下要使用的表结构。 public class Category{ public …