详解ThreadPoolExecutor与ScheduledThreadPoolExecutor

来源:互联网 发布:湖南文艺出版社 知乎 编辑:程序博客网 时间:2024/05/16 18:47

一.ThreadPoolExecutor

通过Executors,可以创建3种类型的ThreadPoolExecutor。
- FixedThreadPool
- SingleThreadExecutor
- CachedThreadPool

1.FixedThreadPool
FixedThreadPool被称为可重用固定线程数的线程池。下面是FixedThreadPool的源代码实现。

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

FixedThreadPool中多余的空闲线程会被立即终止。
FixedThreadPool的execute()运行示意图如下所示。

  1. 如果当前运行的线程数小于corePoolSize,则创建新线程来执行任务。
  2. 当前运行的线程数等于corePoolSize,将任务加入LinkedBlockingQueue。
  3. 线程执行完1中的任务后,会反复从阻塞队列中取任务执行。

FixedThreadPool使用无界队列LinkedBlockingQueue(容量为Integer.MAX_VALUE),使用无界队列会对线程池带来以下影响。
1. 当线程池内的线程数达到corePoolSize后,新任务将在阻塞队列中等待,因此线程池内的线程数不会超过corePoolSize;
2. 由于1,使用无界队列时maximumPoolSize将是一个无效参数;
3. 由于1和2,使用无界队列时keepAliveTime将是一个无效参数;
4. 由于使用无界队列,运行中的FixedThreadPool(未执行shutdown()或shutdownNow())不会拒绝任务(不会调用RejectedExecutionHandler.rejectedExecution)。

2. SingleThreadExecutor

SingleThreadExecutor是使用单个worker线程的Executor。

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

3.CachedThreadPool

CachedThreadPool是一个会根据需要创建新线程的线程池。

public static ExecutorService newCachedThreadPool() {    return new ThreadPoolExecutor(0, Interger.MAX_VALUE, 60L, TimeUnit.SECONDS,                     new SynchronousQueue<Runnable>());

CachedThreadPool的corePool为空,maximumPool无界,空闲线程等待新任务的最长时间是60s。CachedThreadPool使用没有容量的SysnchronousQueue作为线程池的工作队列,但maximumPool无界,意味着,如果主线程提交任务的速度高于maximumPool中线程处理任务的速度时,cachedThreadPool会不断创建新的线程。极端情况下,会因为创建线程过多而耗尽CPU和内存资源。
CachedThreadPool的execute()方法执行示意图如下所示。

如上图所示。
1. 首先执行SynchronousQueue.offer(Runnable task)。如果当前maximumPool中有空闲线程正在执行SynchronousQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS),那么主线程执行offer操作与空闲线程执行的poll操作配对成功,主线程把任务交给空闲线程执行。否则执行下面的步骤2.
2. 当初始maximumPool为空,或者maximumPool中没有空闲的线程时,将没有线程执行SynchronousQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS),这种情况下步骤1将失败。此时CachedThreadPoll会创建一个新线程执行任务。
3. 在步骤2中新创建的线程将任务执行完之后,会执行SynchronousQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)。这个poll操作会让空闲线程最多在SynchronousQueue中等待60S。

二. ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor主要用来在给定的延迟之后运行任务,或者定期执行任务。其执行示意图如下图所示。

DelayQueue是一个无界队列,所以ThreadPoolExecutor的maximumPoolSize在ScheduledThreadPoolExecutor中没什么意义。
ScheduledThreadPoolExecutor的执行主要分为两大部分。
- 当调用ScheduledThreadPoolExecutor的scheduleAtFixedRate()方法或者scheduleWithFixedDelay()方法时,会向ScheduledThreadPoolExecutor的DelayQueue添加一个实现了RunnableScheduledFuture接口的ScheduledFutureTask.
- 线程池中的线程从DelayQueue中获取ScheduledFutureTask, 然后执行任务。

ScheduledFutureTask主要包含3个成员变量,如下。
- long型成员变量time,表示这个任务将要被执行的具体时间
- long型成员变量sequenceNumber,表示这个任务被添加到ScheduledThreadPoolExecutor中的序号。
- long型成员变量period,表示任务执行的时间间隔。

DelayQueue封装了一个PriorityQueue, 这个PriorityQueue会对队列中的ScheduledFutureTask进行排序。排序时, time小的排在前面,若time相同,则sequenceNumber小的排在前面。

ScheduledThreadPoolExecutor中的线程A执行某个周期任务分为4个步骤;
1. 线程A从DelayQueue中获取已经到期的ScheduledFutureTask(DelayQueue.take());
2. 执行这个任务;
3. 修改这个任务的time变量为下次将要执行的时间;
4. 把该任务放回DelayQueue中(DelayQueue.add())。

参考《Java并发编程的艺术》

0 0
原创粉丝点击