- 浏览: 87247 次
- 性别:
- 来自: 厦门
最新评论
-
zhengyong7232:
SELECT t.province,DECODE(landsc ...
列转行 -
meepo3927:
不是特么很懂
列转行
工作中多处接触到了ThreadPoolExecutor。趁着现在还算空,学习总结一下。
前记:
- jdk官方文档(javadoc)是学习的最好,最权威的参考。
- 文章分上中下。上篇中主要介绍ThreadPoolExecutor接受任务相关的两方面入参的意义和区别,池大小参数corePoolSize和maximumPoolSize,BlockingQueue选型(SynchronousQueue,
LinkedBlockingQueue,
ArrayBlockingQueue
);中篇中主要聊聊与keepAliveTime这个参数相关的话题;下片中介绍一下一些比较少用的该类的API,及他的近亲:ScheduledThreadPoolExecutor。 - 如果理解错误,请直接指出。
查看JDK帮助文档,可以发现该类比较简单,继承自AbstractExecutorService,而AbstractExecutorService实现了ExecutorService接口。
ThreadPoolExecutor的完整构造方法的签名是:
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
先记着,后面慢慢解释。
===============================神奇分割线==================================
其实对于ThreadPoolExecutor的构造函数网上有N多的解释的,大多讲得都很好,不过我想先换个方式,从Executors这个类入手。因为他的几个构造工厂构造方法名字取得令人很容易了解有什么特点。但是其实Executors类的底层实现便是ThreadPoolExecutor!
ThreadPoolExecutor是Executors类的底层实现。
在JDK帮助文档中,有如此一段话:
“强烈建议程序员使用较为方便的 Executors
工厂方法 Executors.newCachedThreadPool()
(无界线程池,可以进行自动线程回收)、Executors.newFixedThreadPool(int)
(固定大小线程池)和 Executors.newSingleThreadExecutor()
(单个后台线程),它们均为大多数使用场景预定义了设置。”
可以推断出ThreadPoolExecutor与Executors类必然关系密切。
===============================神奇分割线==================================
OK,那就来看看源码吧,从newFixedThreadPool开始。
ExecutorService newFixedThreadPool(int nThreads):固定大小线程池。
可以看到,corePoolSize和maximumPoolSize的大小是一样的(实际上,后面会介绍,如果使用无界queue的话maximumPoolSize参数是没有意义的),keepAliveTime和unit的设值表名什么?-就是该实现不想keep alive!最后的BlockingQueue选择了LinkedBlockingQueue,该queue有一个特点,他是无界的。
- public static ExecutorService newFixedThreadPool(int nThreads) {
- return new ThreadPoolExecutor(nThreads, nThreads,
- 0L, TimeUnit.MILLISECONDS,
- new LinkedBlockingQueue<Runnable>());
- }
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
ExecutorService newSingleThreadExecutor():单线程。
可以看到,与fixedThreadPool很像,只不过fixedThreadPool中的入参直接退化为1
- public static ExecutorService newSingleThreadExecutor() {
- return new FinalizableDelegatedExecutorService
- (new ThreadPoolExecutor(1, 1,
- 0L, TimeUnit.MILLISECONDS,
- new LinkedBlockingQueue<Runnable>()));
- }
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
ExecutorService newCachedThreadPool():无界线程池,可以进行自动线程回收。
这个实现就有意思了。首先是无界的线程池,所以我们可以发现maximumPoolSize为big big。其次BlockingQueue的选择上使用SynchronousQueue。可能对于该BlockingQueue有些陌生,简单说:该QUEUE中,每个插入操作必须等待另一个
线程的对应移除操作。比如,我先添加一个元素,接下来如果继续想尝试添加则会阻塞,直到另一个线程取走一个元素,反之亦然。(想到什么?就是缓冲区为1的生产者消费者模式^_^)
注意到介绍中的自动回收线程的特性吗,为什么呢?先不说,但注意到该实现中corePoolSize和maximumPoolSize的大小不同。
- public static ExecutorService newCachedThreadPool() {
- return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
- 60L, TimeUnit.SECONDS,
- new SynchronousQueue<Runnable>());
- }
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
===============================神奇分割线==================================
到此如果有很多疑问,那是必然了(除非你也很了解了)
先从BlockingQueue<Runnable> workQueue这个入参开始说起。在JDK中,其实已经说得很清楚了,一共有三种类型的queue。以下为引用:(我会稍微修改一下,并用红色突出显示)
BlockingQueue
都可用于传输和保持提交的任务。可以使用此队列与池大小进行交互:
- 如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。(什么意思?如果当前运行的线程小于corePoolSize,则任务根本不会存放,添加到queue中,而是直接抄家伙(thread)开始运行)
- 如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。
- 如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。
-
直接提交。工作队列的默认选项是
SynchronousQueue
,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。 -
无界队列。使用无界队列(例如,不具有预定义容量的
LinkedBlockingQueue
)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。 -
有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如
ArrayBlockingQueue
)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。
===============================神奇分割线==================================
到这里,该了解的理论已经够多了,可以调节的就是corePoolSize和maximumPoolSizes 这对参数还有就是BlockingQueue的选择。
例子一:使用直接提交策略,也即SynchronousQueue。
首先SynchronousQueue是无界的,也就是说他存数任务的能力是没有限制的,但是由于该Queue本身的特性,在某次添加元素后必须等待其他线程取走后才能继续添加。在这里不是核心线程便是新创建的线程,但是我们试想一样下,下面的场景。
我们使用一下参数构造ThreadPoolExecutor:
- new ThreadPoolExecutor(
- 2, 3, 30, TimeUnit.SECONDS,
- new <SPAN style="WHITE-SPACE: normal">SynchronousQueue</SPAN><Runnable>(),
- new RecorderThreadFactory("CookieRecorderPool"),
- new ThreadPoolExecutor.CallerRunsPolicy());
new ThreadPoolExecutor(
2, 3, 30, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new RecorderThreadFactory("CookieRecorderPool"),
new ThreadPoolExecutor.CallerRunsPolicy());
当核心线程已经有2个正在运行.
- 此时继续来了一个任务(A),根据前面介绍的“如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。”,所以A被添加到queue中。
- 又来了一个任务(B),且核心2个线程还没有忙完,OK,接下来首先尝试1中描述,但是由于使用的SynchronousQueue,所以一定无法加入进去。
- 此时便满足了上面提到的“如果无法将请求加入队列,则创建新的线程,除非创建此线程超出maximumPoolSize,在这种情况下,任务将被拒绝。”,所以必然会新建一个线程来运行这个任务。
- 暂时还可以,但是如果这三个任务都还没完成,连续来了两个任务,第一个添加入queue中,后一个呢?queue中无法插入,而线程数达到了maximumPoolSize,所以只好执行异常策略了。
LinkedBlockingQueue
OK,此时任务变加入队列之中了,那什么时候才会添加新线程呢?
这里就很有意思了,可能会出现无法加入队列吗?不像SynchronousQueue那样有其自身的特点,对于无界队列来说,总是可以加入的(资源耗尽,当然另当别论)。换句说,永远也不会触发产生新的线程!corePoolSize大小的线程数会一直运行,忙完当前的,就从队列中拿任务开始运行。所以要防止任务疯长,比如任务运行的实行比较长,而添加任务的速度远远超过处理任务的时间,而且还不断增加,如果任务内存大一些,不一会儿就爆了,呵呵。
可以仔细想想哈。
例子三:有界队列,使用ArrayBlockingQueue。
这个是最为复杂的使用,所以JDK不推荐使用也有些道理。与上面的相比,最大的特点便是可以防止资源耗尽的情况发生。
举例来说,请看如下构造方法:
- new ThreadPoolExecutor(
- 2, 4, 30, TimeUnit.SECONDS,
- new ArrayBlockingQueue<Runnable>(2),
- new RecorderThreadFactory("CookieRecorderPool"),
- new ThreadPoolExecutor.CallerRunsPolicy());
new ThreadPoolExecutor( 2, 4, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(2), new RecorderThreadFactory("CookieRecorderPool"), new ThreadPoolExecutor.CallerRunsPolicy());
假设,所有的任务都永远无法执行完。
对于首先来的A,B来说直接运行,接下来,如果来了C,D,他们会被放到queu中,如果接下来再来E,F,则增加线程运行E,F。但是如果再来任务,队列无法再接受了,线程数也到达最大的限制了,所以就会使用拒绝策略来处理。
总结:
- ThreadPoolExecutor的使用还是很有技巧的。
- 使用无界queue可能会耗尽系统资源。
- 使用有界queue可能不能很好的满足性能,需要调节线程数和queue大小
- 线程数自然也有开销,所以需要根据不同应用进行调节。
- 数量大,但是执行时间很短
- 数量小,但是执行时间较长
- 数量又大执行时间又长
- 除了以上特点外,任务间还有些内在关系
发表评论
-
最小生成树之Kruskal算法
2013-12-15 00:54 1058这篇文章实现最小生成树的Kruskal算法Kruskal算 ... -
排序算法--插入排序
2013-12-15 00:47 1026插入排序原理:假设 ... -
poi创建下拉框
2013-04-16 19:01 0package finance.tools.salesper ... -
Spring中Transactional配置
2013-04-11 20:43 967Spring中Transactional配置 标签: 杂谈 分 ... -
Hibernate将Enum枚举类型映射为Int类型 .
2013-04-09 16:46 1497最近要用Hibernate做一个枚举类型的映射,在MySQL ... -
Hibernate 更灵活的用法 HibernateCallback
2013-01-22 14:41 1380在项目中用Hibernate的DetachedCriter ... -
spring源代码分析(2)--BeanFactory
2013-01-20 12:38 1377我们首先来看下BeanFacroty接口 pa ... -
spring源代码分析(1)--Resource的分析
2013-01-20 12:22 3996我们知道,在spring中,配置文件是通过资源形式加载的, ... -
DefaultListableBeanFactory研究
2013-01-18 10:49 5866DefaultSingletonBeanRegistry完 ... -
jvm的内存调优
2013-01-11 15:54 8251) 堆 运行时数 ... -
struts2中Action的name 和package的name和namespace是什么作用
2013-01-02 12:16 9594在struts2的 struts.xml 配置中 例: ... -
深入探索 高效的Java异常处理框架
2012-10-22 14:09 869摘要:本文从Java异常最基本的概念、语法开始讲述了Java异 ... -
JAVA对ArrayList排序
2012-10-16 21:53 16478java如何对ArrayList中对象按照该对象某属性排序 ... -
ThreadPoolExecutor使用和思考(中)-keepAliveTime及拒绝策略
2012-09-11 00:48 922工作中多处接触到了ThreadPoolExecutor。趁着现 ... -
Spring IOC之BeanFactory
2012-09-10 12:39 1280BeanFactory ,作为 Spring 基础的 IoC ... -
Java5 并发学习
2012-09-10 11:33 787在Java5之后 ... -
Spring对事务管理的支持的发展历程(基础篇
2012-08-31 17:43 907Connection conn = DataS ... -
CXF ,在Client端用于把 Date 类型转换成 XMLGregorianCalendar .
2012-08-31 16:54 1612import javax.xml.datatype.Datat ... -
《Spring技术内幕》学习笔记10——Web环境中Spring的启动过程
2012-07-30 09:04 9761.Spring 不但可以在 JavaSE 环境中应用,在 W ... -
spring 加载bean过程源码简易解剖
2012-07-30 08:45 1169这一篇主要是讲用载入bean的过程。其实就是IOC.低调 低调 ...
相关推荐
11-线程池 ThreadPoolExecutor 底层原理源码分析(上)-周瑜.pdf 12-线程池 ThreadPoolExecutor底层原理源码分析(下)-周瑜.pdf 13、线程池 ForkJoinPool实战及其工作原理分析 (1).pdf 14、深入理解井发可见性、...
11-线程池 ThreadPoolExecutor 底层原理源码分析(上)-周瑜.pdf 12-线程池 ThreadPoolExecutor底层原理源码分析(下)-周瑜.pdf 13、线程池 ForkJoinPool实战及其工作原理分析 (1).pdf 14、深入理解井发可见性、...
ThreadPoolExecutor的使用和Android常见的4种线程池使用介绍
在《阿里巴巴java开发手册》中...另外由于前面几种方法内部也是通过ThreadPoolExecutor方式实现,使用ThreadPoolExecutor有助于大家明确线程池的运行规则,创建符合自己的业务场景需要的线程池,避免资源耗尽的风险。
ThreadPoolExecutor使用和思考
线程是系统中可执行调度的...线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,规避资源耗尽的风险。集合的详细描述,以及集合中的异同点,HashMap不同jdk版本区别,ConcurrentHashMap介绍。
线程池ThreadPoolExecutor实战及其原理分析(下)线程池ThreadPoolExecutor实战及其原理分析(下)线程池ThreadPoolExecutor实战及其原理分析(下)线程池ThreadPoolExecutor实战及其原理分析(下)线程池ThreadPoolExecutor...
死磕ThreadPoolExecutor线程池.pdf!!死磕ThreadPoolExecutor线程池.pdf死磕ThreadPoolExecutor线程池.pdf死磕ThreadPoolExecutor线程池.pdf
Reference: 《创建Java线程池》[1],《Java线程:新特征-线程池》[2], 《Java线程池学习》[3],《线程池ThreadPoolExecutor使用简介》[4],《Java5中的线程池实例讲解》[5],《ThreadPoolExecutor使用和思考》[6] ...
线程池的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后... 线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
JDK1[1].5中的线程池(ThreadPoolExecutor)使用简介
11-线程池 ThreadPoolExecutor 底层原理源码分析(上)-周瑜.pdf 12-线程池 ThreadPoolExecutor底层原理源码分析(下)-周瑜.pdf 13、线程池 ForkJoinPool实战及其工作原理分析 (1).pdf 14、深入理解井发可见性、...
线程池原理-ThreadPoolExecutor源码解析 1.构造方法及参数 2.阻塞对列: BlockingQueue 3.线程工厂: DefaultThreadFactory 4.拒绝策略: RejectedExecutionHandler 5.执行线程 Executor
1.资源简介:PyQt5中使用多线程模块QThread解决了PyQt5界面程序执行比较耗时操作时,程序卡顿出现的无响应以及界面输出无法实时显示的问题,采用线程池ThreadPoolExecutor解决了ping多个IP多任务耗时问题。...
ThreadPoolExecutor线程池,有详尽介绍,本人进行过测试,可以使用
一个关于java 线程池的例子,也适合android
b:在实际使用中,服务器在创建和销毁线程上花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一...
NULL 博文链接:https://bijian1013.iteye.com/blog/2284676
11-线程池 ThreadPoolExecutor 底层原理源码分析(上)-周瑜.pdf 12-线程池 ThreadPoolExecutor底层原理源码分析(下)-周瑜.pdf 13、线程池 ForkJoinPool实战及其工作原理分析 (1).pdf 14、深入理解井发可见性、...