title: java线程池
date: 2017-02-03 09:27:23
tags: concurrency
category: concurrency
本篇Blog是《java并发编程的艺术》第九章线程池部分读后总结。
核心参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
corePoolSize(线程池的基本大小):
当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有基本线程,代码如下。
public static void main(String[] args) {
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(1);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 2, 60L, TimeUnit.MINUTES, arrayBlockingQueue);
threadPoolExecutor.prestartAllCoreThreads();
}
maximumPoolSize:线程池的最大线程数
当任务队列已满时,线程池将创建新的线程,直到达到maximumPoolSize
BlockingQueue:核心线程数和最大线程数之间的任务缓冲队列。
比较常见的可使用的阻塞队列:
ArrayBlockingQueue:常见的有界阻塞队列,FIFO
LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于Linked-Block-ingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
maximumPoolSize(线程池最大数量):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。**值得注意的是,如果使用了无界的任务队列这个参数其实就没意义了,因为任务队列永远不会满,则线程数量永远不会大于corePoolSize。
ThreadFactory:创建线程的工厂。
默认即可。guava提高一种可以自定义线程名称的工厂类,可以提高线程的辨识度。
RejectedExecutionHandler(饱和策略):
当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。在JDK 1.5中Java线程池框架提供了以下4种策略。
AbortPolicy:直接抛出异常。
CallerRunsPolicy:只用调用者所在线程来运行任务。
DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
DiscardPolicy:不处理,丢弃掉。
当然也可以实现RejectedExceptionHandler自定义处理策略,发邮件,或者记录下来之类的。
keepAliveTime和TimeUnit:
当线程超过线程池corePoolSize后,在一定的时间内没有任务,则kill掉。这是用来规定时间的。取决于任务进入的频率,合理的时间可以避免频繁创建和结束线程。
线程池的工作流程
ThreadPoolExecutor执行execute方法分下面4种情况。
1)如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁)。
2)如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue。
3)如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务,直到线程数量达到maximumPoolSize。(注意,执行这一步骤需要获取全局锁)。
4)如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用Re-jectedExecutionHandler.rejectedExecution()方法。
ThreadPoolExecutor采取上述步骤的总体设计思路,是为了在执行execute()方法时,尽可能地避免获取全局锁(那将会是一个严重的可伸缩瓶颈)。在ThreadPoolExecutor完成预热之后(当前运行的线程数大于等于corePoolSize),几乎所有的execute()方法调用都是执行步骤2,而步骤2不需要获取全局锁。
线程池的关闭
shutdownNow (试图Interrupt所有的任务)
shutdown(试图Interrupt待执行的任务)
2个方法调用后isShutdwon都返回true,但要注意的是只有任务本身可以被Interrupt,处理了Interrupt信号才能被中断,不能的话,该咋样还是咋样。这和线程的中断是一个道理。
线程池的监控
合理的设置线程池的corePoolSize和maxPoolSize,选择合适的BlockingQueue避免资源浪费和等待任务过多。除了预先估算以外,可以通过监控线程池的状态调整。ThreadPoolExecutor提供了一些方法反返回线程池的关键信息。
比如getPoolSize线程池的当期线程数量,getActiveCount之类的。