Executor框架
1、Executor背景与简介
java的线程既是工作单元,也是执行机制。从JDK 5开始,把工作单元与执行机制分离开来。
工作单元包括Runnable和Callable,而执行机制由Executor框架提供。
executor框架的两级调度模型:
在上层,Java多线程程序通常把应用分解为若干个任务,然后使用用户级的调度器(executor框架)将这些任务映射为固定数量的线程;
在底层,操作系统内核将这些线程映射到硬件处理器上。
应用程序通过executor框架控制上层的调度,下层的调度由操作系统内核控制,下层不受应用程序的控制。
2、Executor框架的结构
Executor框架的结构主要由3大部分组成:
a、任务:包括被执行任务需要实现的接口(Runnable或Callable)。
b、任务的执行:包括任务执行机制的核心接口Executor,还有继承自Executor的ExecutorService接口。Executor框架有两个关键类实现了ExecutorService接口(ThreadPoolExecutor和ScheduledThreadPoolExecutor)。
c、异步计算的结果:包括接口Future和实现Future接口的FutureTask类。
executor框架包含的主要的类与接口如图:
下面是这些类与接口的简介。
Executor:是一个接口,是Executor框架的基础,它将任务的提交与任务的执行分离开来。
ThreadPoolExecutor:是线程池的核心实现类,用来执行被提交的任务。
ScheduleThreadPoolExecutor:是一个实现类,可以在给定的延迟后运行命令,或定期执行命令。它比Timer灵活,功能更强大。
Future接口,FutureTask实现类:代表异步计算的结果。
Runnable接口和Callable接口的实现类:可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。
Executor框架使用示意图如下:
a、主线程首先创建任务对象,任务对象实现了Runnable或者Callable接口。
Executors可以把一个Runnable对象封装为一个Callable对象,通过Executor.callable(Runnable task)或Executors.callable(Runnable task, T result))。
b、然后把Runnable对象直接交给ExecutorService执行,ExecutorService.execute(Runnable command))或者ExecutorService.submit(Runnable | Callable<T> task)。执行submit方法,ExecutorService将返回一个实现Future接口的对象,一般是FutureTask对象。
c、最后,主线程通过执行FutureTask.get()方法来等待任务执行完成。主线程也可以执行FutureTask.cancel(boolean mayInterruptIfRunning)来取消此任务的执行。
3、ThreadPoolExecutor
ThreadPoolExecutor通常使用工厂类Executors来创建。Executors可以创建3种类型:
a、FixedThreadPool:创建固定线程数。适用为了满足资源管理的需求,需要限制当前线程数量的应用场景,适用于负载比较重的服务器。被称为可重用固定线程数的线程池。实现代码如下:
1 public static ExecutorService newFixedThreadPool(int nThreads) { 2 return new ThreadPoolExecutor(nThreads, nThreads, 3 0L, TimeUnit.MILLISECONDS, 4 new LinkedBlockingQueue<Runnable>()); 5 }
注意corePoolSize和maximumPoolSize都被设置为创建FixedThreadPool时指定的参数nThreads。
注意KeepAliveTime 为0L,表示多余的空闲线程会被立即终止。
注意LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO排序元素。
在图中的1表示:如果当前运行的线程数少于corePoolSize,则创建新的线程来执行任务。
在图中的2表示:在线程池完成预热之后(当前运行的线程数等于corePoolSize),将任务加入LinkedBlockingQueue。
在图中的3表示:线程在第一次创建后执行完任务后,会在循环中反复从LinkedBlockingQueue获取任务来执行。
b、SingleThreadExecutor:创建单个线程。适用需要保证顺序执行各个任务;在任意时间点,不会有多个线程是活动的应用场景。是单个worker线程的Executor。使用无界队列LinkedBlockingQueue作为线程池的工作队列(队列的容量是Integer.MAX_VALUE)。
1 public static ExecutorService newSingleThreadExecutor() { 2 return new FinalizableDelegatedExecutorService 3 (new ThreadPoolExecutor(1, 1, 4 0L, TimeUnit.MILLISECONDS, 5 new LinkedBlockingQueue<Runnable>())); 6 }
在图中的1表示:如果当前运行的线程数少于corePoolSize(即线程池中无运行的线程),则创建一个新的线程来执行任务。
在图中的2表示:在线程池完成预热之后(当前线程池中有一个运行的线程),将任务加入LinkedBlockingQueue。
在图中的3表示:线程在第一次创建后执行完任务后,会在循环中反复从LinkedBlockingQueue获取任务来执行。
c、CachedThreadPool:创建大小无界的线程池,适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器。是一个会根据需要创建新线程的线程池。
1 public static ExecutorService newCachedThreadPool() { 2 return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 3 60L, TimeUnit.SECONDS, 4 new SynchronousQueue<Runnable>()); 5 }
这里keepAliveTime为60L,单位是TimeUnit.SECONDS,表示CachedThreadPool中的空闲线程等待新任务的最长时间为60秒,空闲线程超过60秒后将会被终止。
FixedThreadPool和SingleThreadExecutor使用无界队列LinkedBlockingQueue作为线程池的工作队列。CachedThreadPool使用没有容量的SynchronousQueue作为线程池的工作队列,但CachedThreadPool的maximumPool是无界的。意味着如果主线程提交任务的速度高于maximumPool中线程处理任务的速度时,CachedThreadPool会不断创建新线程。
在图中的1表示:首先执行SynchronousQueue.offer(Runnable task)。如果当前maximumPool中有空闲线程正在执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),那么主线程执行offer操作与空闲线程执行的poll操作配对成功,主线程把任务交给空闲线程执行,execute()方法执行完成;否则执行下面的步骤2)。
在图中的2表示:当初始maximumPool为空或者maximumPool中当前没有空闲线程时,将没有线程执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。这种情况下,步骤1)将失败。此时CachedThreadPool会创建一个新线程执行任务,execute()方法执行完成。
在图中的3表示:在步骤2)中新创建的线程将任务执行完后,会执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。这个poll操作会让空闲线程最多在SynchronousQueue中等待60秒钟。如果在60秒钟内主线程提交了一个新任务(主线程执行步骤1),那么这个空闲线程将执行主线程提交的任务;否则,这个空闲线程将终止。由于空闲60秒的线程会被终止,因此长时间保持空闲的CachedThreadPool不会使用任何资源。
4、ScheduledThreadPoolExecutor
该类主要用来在给定的延迟之后运行任务,或者定期执行任务。
通常使用工厂类Executors来创建2种不同的类型:
ScheduledThreadPoolExecutor:包含若干个线程,适用于需要多个后台线程执行周期任务,同时为了满足资源管理的需求而需要限制后台线程的数量的应用场景。
SingleThreadScheduledExecutor:包含一个线程,适用于需要单个后台线程执行周期任务,同时需要保证顺序地执行各个人物的应用场景。
JDK6中ScheduledThreadPoolExecutor的执行示意图:
DelayQueue是一个无界队列,所以ThreadPoolExecutor的maximumPoolSize在ScheduledThreadPoolExecutor(简称STPE)中没有什么意义。STPE的执行主要分为两大部分。
a、当调用STPE的scheduleAtFixedRate()方法或者ScheduleWithFixedDelay()方法时,会向STPE的DelayQueue添加一个实现了RunnableScheduledFuture接口的ScheduledFutureTask。
b、线程池中的线程从DelayQueue中获取ScheduledFutureTask,然后执行任务。
STPE为了实现周期性的执行任务,对ThreadPoolExecutor做了如下的修改:
a、使用DelayQueue作为任务队列;
b、获取任务的方式不同;
c、执行周期任务后,增加了额外的处理。
STPE会把待调度的任务(ScheduledFutureTask)放到一个DelayQueue中:
ScheduledFutureTask主要包含3个成员变量:
a、long型的time,表示这个任务将要被执行的具体时间;
b、long型的sequenceNumber,表示这个任务被添加到STPE中的序号;
c、long型的period,表示任务执行的间隔周期。
DelayQueue封装了一个PriorityQueue,这个PriorityQueue会对队列中的ScheduledFutureTask进行排序。排序时,time小的排在前面。如果time相同,则sequenceNumber小的排前面。
下图是STPE中的线程1执行某个周期任务的4个步骤:
步骤1:线程1从DelayQueue中获取已到期的ScheduledFutureTask(DelayQueue.take())。到期任务是指ScheduledFutureTask的time大于等于当前时间。
步骤2:线程1执行这个ScheduledFutureTask。
步骤3:线程1修改ScheduledFutureTask的time变量为下次将要被执行的时间。
步骤4:线程1把这个time之后的ScheduledFutureTask放回DelayQueue中(DelayQueue.add())。