Java中的线程既是工作单元,也是执行单元。工作单元包括Runnable和Callable,而执行单元是由Executor框架支持。
ExecutorsService的生命周期有三种状态:运行、关闭和已终止。Executor执行的任务有四个生命周期:创建、提交、开始和完成。
Executor 接口:
void execute(Runnable command);
下面我们来详细看一下ExecutorsService接口
public interface ExecutorService extends Executor {
void shutdown();
List<Runnable> shutdownNow();
boolean isTerminated();
boolean isShutdown();
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
.....
- shutdown():将执行缓慢的关闭过程,不再接受新的任务,同时等待已经提交的任务执行完成————包括那些还未开始执行的任务.
- shutdownNow():将执行除暴的关闭过程:它将尝试取消所有运行的任务;并且不再启动队列中尚未开始执行的任务。
- isTerminated():判断ExecutorService是否进入终止状态
- awaitTermination():当前线程进入阻塞状态,并且等待ExecutorService达到终止状态
- isShutdown():判断ExecutorService是否进入关闭状态
在HotSpot VM的线程模型中,Java线程(java.lang.Thread)被一对一映射为本地操作系统线程。Java线程启动时会创建一个本地操作系统线程;当该Java线程终止时,这个操作系统线程也会被回收。操作系统会调度所有线程并将他们分配给可用的CPU。
两级调度模型指的是:
如上图所示,Executor框架由 3大部分组成:
下面是这些类或者接口的简介:
- Executor是一个接口,它是Executor框架的基础,它将任务的提交与任务的执行分离开来。
- ThreadPoolExecutor是线程池的核心实现类,用来执行被提交的任务。
- ScheduledThreadPoolExecutor是一个实现类,可以在给定的延迟后运行命令,或者定期执行命令。ScheduledThreadPoolExecutor比Timer更灵活,功能更强大。
- Future接口和实现Future接口的FutureTask类,代表异步计算的结果。
- Runnable接口和Callable接口的实现类,都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。
下面是Executor框架使用示意图:
使用流程:
Executors.callable(Runnable)
可以把Runnable对象包装成Future对象。ExecutorService.submit(Runnable / Callable)
或 ExecutorService.execute(Runnable)
,把任务提交给ExecutorService。submit()
会返回一个FutureTask对象,主线程可调用FutureTask.get()
使主线程等待任务执行完成,也可以调用FutureTask.cancel(boolean)
取消任务执行。Executor接口仅仅只有一个execute(Runnable)
方法,ExecutorService接口是继承该接口的一个子接口。
线程池的核心实现类,用来执行被提交的任务。
是ThreadPoolExecutor的一个子类,只是ThreadPoolExecutor的一个简单封装。可以在给定的延迟后运行命令,或者定期指定命令,比 工具类Timer更加灵活、功能更强大。
都是任务,可以提交给ExecutorService执行。
Callable 可以返回结果 且 可抛出异常,Runnable 则不可以。调用Executors.callable(Runnable)
可以把Runnable对象包装成Future对象。
Future接口 和 FutureTask实现类 是用来表示异步计算的结果的。JDK 1.8为止,调用submit()
会返回一个FutureTask对象。
Executors工厂类可以,
目的是为了创建固定线程数量的线程池,适用于负载比较重的服务器,使用了无界队列LinkedBlockingQueue。
使用Executors工厂类创建的源代码为:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
可知:
其执行流程如下:
目的是为了创建固定线程数量的线程池,适用于负载比较重的服务器,使用了无界队列LinkedBlockingQueue。
使用Executors工厂类创建的源代码为:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
可知:
其执行流程如下:
使用了无界队列LinkedBlockingQueue,目的是创建使用单个线程的Executor,适用于需要保证各个任务顺序执行且不会有多个线程活动的应用场景。
使用Executors工厂类创建的源代码为:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
使用无界队列LinkedBlockingQueue带来的影响同上。
其执行流程如下:
流程说明略。
CachedThreadPool 是一个会根据需求创建新线程的线程池,是一个线程数量无界的线程池。适用于 执行很多的短任务的小程序 或者 负载较轻的服务器。使用的是无容量的无界队列SynchronousQueue。
使用Executors工厂类创建的源代码为:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
说明:
其执行流程如下:
流程说明:
SynchronousQueue.offer(Runnable)
。如果 maximumPool中有空闲线程 执行了 poll()
,那么与主线程执行的offer()
配对成功,主线程则把任务直接提交给空闲线程执行,execute()
执行完成。否则,执行步骤2.poll()
,这种情况下CachedThreadPool 会创建一个新线程执行任务,execute()
执行完成。poll()
,会让空闲线程最多在 SynchronousQueue 中等待60s,
ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,主要用来给 给定延迟之后运行任务,或者定期执行任务。适用于 需要多个后台执行周期任务 且 又需要限制线程数量 的应用场景。
使用了无界队列 DelayQueue。
功能与Timer类似,但区别有:
使用Executors工厂类创建的源代码为:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
说明:其中corePoolSize是指定的后台线程的基本数量(但是它的maximumPool是无界的)。
ScheduledThreadPoolExecutor的构造函数如下:
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
newSingleThreadScheduledExecutor的构造函数如下:
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
ScheduledThreadPoolExecutor 的任务是直接增加到 DelayQueue队列中,然后由coolPool的线程以一定方式获取任务执行,是一次任务传递,而不是直接交付给线程执行,其流程如下:
为了实现周期性的执行任务,对ThreadPoolExecutor做了3个方面的修改:
主线程调用 scheduleAtFixedRate()
或 scheduleWithFixedDelay()
提交任务(一个ScheduledFutureTask对象)直接到DelayQueue队列中。
ScheduledFutureTask主要由3个成员变量:
成员变量 | 描述 |
---|---|
long time | 任务被执行的时间 |
long sequenceNumber | 任务添加到线程池的序号 |
long period | 任务执行的间隔周期 |
DelayQueue 封装了一个PriorityQueue,这个优先队列会对任务列表的任务排序,排序规则如下:
线程执行某个任务的步骤:
DelayQueue.take()
,线程1 从DelayQueue获取已到期的任务。到期任务指的是 time大于等于当前时间的任务。DelayQueue.add()
。FutureTask 实现了2个接口:Future 、 Runnable。因此,
FutureTask.run()
直接启动线程运行。调用FutureTask.run()
有3种状态:未启动、已启动 和 已完成,其状态迁移图如下:
在3种状态下,调用get()
和cancel()
方法的执行示意图如下:
FutureTask的实现基于AbstractQueuedSynchronizer(以下简称为AQS)。java.util.concurrent中的很多可阻塞类(比如ReentrantLock)都是基于AQS来实现的。AQS是一个同步框架,它提供通用机制来原子性管理同步状态、阻塞和唤醒线程,以及维护被阻塞线程的队列。
AQS被作为“模板方法模式”的基础类提供给FutureTask的内部子类Sync,这个内部子类只
需要实现状态检查和状态更新的方法即可,这些方法将控制FutureTask的获取和释放操作。具体来说,Sync实现了AQS的tryAcquireShared(int)方法和tryReleaseShared(int)方法,Sync通过这两个方法来检查和更新同步状态。
FutureTask的设计示意图如图所示。
FutureTask.get()方法会调用AQS.acquireSharedInterruptibly(int arg)方法,这个方法的执行过程如下。
- 调用AQS.acquireSharedInterruptibly(int arg)方法,这个方法首先会回调在子类Sync中实现的tryAcquireShared()方法来判断acquire操作是否可以成功。acquire操作可以成功的条件为:state为执行完成状态RAN或已取消状态CANCELLED,且runner不为null。
- 如果成功则get()方法立即返回。如果失败则到线程等待队列中去等待其他线程执行
release操作。- 当其他线程执行release操作(比如FutureTask.run()或FutureTask.cancel(…))唤醒当前线程后,当前线程再次执行tryAcquireShared()将返回正值1,当前线程将离开线程等待队列并唤醒它的后继线程(这里会产生级联唤醒的效果,后面会介绍)。
- 最后返回计算的结果或抛出异常。
FutureTask.run()的执行过程如下。
- 执行在构造函数中指定的任务(Callable.call())。
- 以原子方式来更新同步状态(调用AQS.compareAndSetState(int expect,int update),设置state为执行完成状态RAN)。如果这个原子操作成功,就设置代表计算结果的变量result的值为Callable.call()的返回值,然后调用AQS.releaseShared(int arg)。
- AQS.releaseShared(int arg)首先会回调在子类Sync中实现tryReleaseShared(arg)来执行release操作(设置运行任务的线程runner为null,然会返回true);AQS.releaseShared(int arg),然后唤醒线程同步等待队列中的第一个线程。
- 调用FutureTask.done()。
当执行FutureTask.get()方法时,如果FutureTask不是处于执行完成状态RAN或已取消状态CANCELLED,当前执行线程将到AQS的线程等待队列中等待(见下图的线程A、B、C和D)。当某个线程执行FutureTask.run()方法或FutureTask.cancel(…)方法时,会唤醒线程等待队列的第一个线程(见图所示的线程E唤醒线程A)。
假设开始时FutureTask处于未启动状态或已启动状态,等待队列中已经有3个线程(A、B和
C)在等待。此时,线程D执行get()方法将导致线程D也到等待队列中去等待。
当线程E(运行任务的线程Runner)执行run()方法时,会唤醒队列中的第一个线程A。线程A被唤醒后,首先把自己从队列中删除,然后唤醒它的后继线程B,最后线程A从get()方法返回。线程B、C和D重复A线程的处理流程。最终,在队列中等待的所有线程都被级联唤醒并从get()方法返回。