【Java学习笔记】ThreadPoolExecutor 翻译
一、资源
既然读文档是最好的,那就逼自己好好读一遍,自己学习而已....
http://download.oracle.com/javase/1,5.0/docs/api/java/util/concurrent/ThreadPoolExecutor.html
二、翻译
类 ThreadPoolExecutor
ExecutorService实现用可使用的线程来实行提交的任务,一遍用
Executors进行配置
线程池通常用来解决两个问题:
- 由于减小了任务执行前得开销,他们通常用来优化大量同步任务的执行
- 在执行一系列任务的时候,他们给资源的绑定以及管理(包括那些线程)提供了一些方法
每个ThreadPoolExecutor通常都有一些基本的统计数据,比如任务数目
为了使线程池在程序中的多个页面都有效,此类提供了一些参数和可扩展的hooks进行调节。
然而,建议程序员使用更方便的方法,如Executors.newCachedThreadPool()(无解线程池,可自动进行线程回收),
Executors.newFixedThreadPool(int)
(固定大小的线程池), Executors.newSingleThreadExecutor()(单任务的线程池),
,用这些方法来设定最常用的一些设定(the most common usage scenarios).否则,参照些列知道惊醒手动配置:
- 核心以及最线程池的上限
ThreadPoolExecutor会根据corePoolSize和maxiumPoolSize设定的边界来调节池的大小。
当新的任务通过 execute(java.lang.Runnable提交的时候,
如果当前执行的线程数目少于
corePoolSize, 则计算其他辅助线程(worker threads)是空闲的,系统还是会新开线程来处理这个请求。
如果当前在跑的线程数多于corePoolSize小于maxiunmPoolSizem,则只有再队列满了的时候,才会开新线程。
如果你把corePoolSize和maxiunmPoolSizem设定成相等的话,你就建立了一个固定大小的线程池。如果maximumPoolSize被设定为无限大(Integer.MAX_VALUE),则此池设定为可容纳任意多的并发线程。
一般情况下,corePoolSize和maxiunmPoolSizem只是在构建的时候进行初始化,但是可以通过setCorePoolSize(int)和
setMaximumPoolSize(int)来动态更改。
- 统一需求构造(one-demand construction)
默认情况下,即使核心线程最初只是在新任务需要时才创建和启动的,也可以使用方法 prestartCoreThread()
或 prestartAllCoreThreads()
对其进行动态重写。
- 创建新线程
新线程都是在 ThreadFactory 中创建的。如果没有特别的构建,都是通过
Executors.defaultThreadFactory()
来创建线程。新建的线程都在同一个 ThreadGroup
中,并且具有相同的优先级NORM_PRIORITY,并且没有守护进程的状态(non-daemon status)。提供别的ThreadFactory,你可以指定线程名,threadgroup,优先级,non-daemon status等等。如果一个ThreadFactory在newThread中返回null,线程创建失败。执行器仍然继续,但是不能再执行任何任务。
- 保持活跃(keep-alive time)
如果一个池中现在运行的线程数多于corePoolSize,如果多出的线程保持空闲的时间大于keepAliveTime(seegetKeepAliveTime(java.util.concurrent.TimeUnit))
,那么这些线程就会被关闭。这样可以在线程池不活跃的时候降低资源的消耗。如果待会线程池又活跃了,新的线程就会被建立。可通过...方法改变这个参量。将此参量设定为Long.MAX_VALUE TimeUnit.NANOSECONDS可以有效的禁止线程
from ever terminating prior to shut down。
- 列队
所有的BlockingQueue都被用来转移和管理已经提交的任务。使用队列来和线程池交互:
1、如果少于corePoolSize数目的线程正在执行,Executor会新建线程而不是用队列
2、如果大于等于corePoolSize的线程在执行,Executor会使用队列提交消息而不是新建线程。
3、如果请求不能放到队列中,则会建立一个新线程除非,这样做会超过maximumPoolSize,这种情况下,这个任务直接拒绝执行。
有三个基本的队列策略:
1、直接提交。一个好的默认选择是 SynchronousQueue
,他直接将任务交给线程而不保留。这种情况下,如果没有线程来执行此任务,则任务直接失败,所以新的线程会被建立。此策略可以避免在处理可能具有内部依赖性的请求集合时出现锁定。直接提交要求无界池以防新任务提交被拒绝。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
2、无界队列。使用无界的队列(LinkedBlockingQueue),则在所有的corPoolSize量的线程繁忙时,新任务会直接加入队列中。这种情况下,
maximumPoolSize完全没有用。当各个任务相互独立的时候,由于任务之间不会影响其他任务的执行,所以这样处理很好,例如,在某个web服务端。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
3、有界队列。 有界队列( ArrayBlockingQueue)通过
maximumPoolSize的限制可有效防止资源耗尽,但是有界队列很难控制。队列大小和池的上限可能要相互折中:大队列+小池子可以降低CPU消耗,OS资源,和页面转换的预先开销,但是可能降低吞吐量(artificially low throughput)。如果任务频繁阻塞(比如I/0端口),则系统很可能会消耗比超过你预想的更多的时间(a system may be able to schedule time for more threads than you otherwise allow)。用小队列通常需要大池子,这样会使得CPU更忙但是可能遇见不可接受的开销,这些考小也会较低吞吐量。(Use of small queues generally requires larger pool sizes, which keeps CPUs busier but may encounter unacceptable scheduling overhead, which also decreases throughput.)
- 被拒绝的任务
当Executor被关闭或者线程数和队列容量都达到了预定义的上限时已经饱和(saturated),通过execute(java.lang.Runnable)提交的新任务会被拒绝.以上任一情况下,
execute()方法会调用RejectedExecutionHandler的
RejectedExecutionHandler.rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor)。默认的预定义的策略如下:
1、默认的ThreadPoolExecutor.AbortPolicy 中,hanlder在遇到拒绝时,抛出runtime异常
RejectedExecutionException
2、 ThreadPoolExecutor.CallerRunsPolicy中,调用了execute方法的线程来执行这个任务。
此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。(This provides a simple feedback control mechanism that will slow down the rate that new tasks are submitted.)
3、 ThreadPoolExecutor.DiscardPolicy中,不能执行的任务直接被抛掉。
4、 ThreadPoolExecutor.DiscardOldestPolicy中,如果excutor没有关闭,那么工作队列头部的任务将被抛弃,然后继续尝试提交。(周而复始)
你可以自定义并且使用各种 RejectedExecutionHandler,这样做要很谨慎,特别是这个策略只在特定的容量或者只是队列策略。
- Hook方法
略
- 队列维持
重点就是不要再程序中自己特意去操控,略。