1. 简介
1.1 两级调度模型
应用程序通过Executor框架控制上层的调度,将任务映射为固定数量的线程;
下层调度由操作系统内核控制,将线程映射到硬件处理器上,不受应用程序控制。
1.2 Executor框架的结构与成员
1.2.1 结构
1.2.1.1 三大组成
- 任务: 包括被执行任务需要实现的接口:Runnable接口或Callable接口。
- 任务的执行: 包括任务执行机制的核心接口Executor,以及继承自Executor的ExecutorService接口。Executor框架由两个关键类实现了ExecutorService接口(ThreadPoolExecutor和ScheduledThreadPoolExecutor)
- 异步计算的结果: 包括接口 Future 和 实现 Future接口的 FutureTask 类。
1.2.1.2 类和接口
- Executor 接口为框架的基础,将任务的提交与执行分离开来。
- ThreadPoolExecutor 是线程池的核心实现类,用来执行被提交的任务。
- ScheduledThreadPoolExecutor 是一个实现类,可以在给定延迟后运行命令,或定期执行命令。比 Timer 更灵活,功能更强大。
- Future 接口和实现 Future 接口的 FutureTask 类,代表异步计算的结果。
- Runnable 接口和 Callable 接口的实现类,都可以被 ThreadPoolExecutor 或 ScheduledThreadPoolExecutor 执行。
1.2.1.3 使用
- 主线程首先要创建实现Runnable或Callable接口的对象。
- 工具类 Executors 可以把一个Runnable对象封装成一个Callable对象:
- Executors.callable(Runnable task)
- Executors.callable(Runnable task, Object resule)
- 工具类 Executors 可以把一个Runnable对象封装成一个Callable对象:
- 然后可以交给ExecutorService执行。
- ExecutorService.execute(Runnable command),入参只是Runnable对象。
- ExecutorService.submit(Runnable task) 或 ExecutorService.submit(Callable task)。
- ExecutorService.submit(…)方法返回一个实现Future接口的对象(目前基本上是FutureTask对象)。由于FutureTask也实现了Runnable,故可以创建FutureTask交给ExecutorService执行。
- 最后主线程可以执行 FutureTask.get() 方法来等待任务执行完成。
- 也可执行FutureTask.cancel(boolean mayInterruptIfRunning)来取消这次任务的执行。
1.2.2 成员
包括:
- ThreadPoolExecutor
- FixedThreadPool
- SingleThreadExecutor
- CachedThreadPool
- ScheduledThreadPoolExecutor
- ScheduledThreadPoolExecutor
- SingleThreadScheduledExecutor
- Future 接口
- Runnable 接口
- Callable 接口
- Executors
2. ThreadPoolExecutor 详解
2.1 介绍
Executor框架最核心的类,是线程池的实现类,由以下四个组件构成:
- corePool: 核心线程池的大小。
- maximumPool: 最大线程池的大小。
- BlockingQueue: 用来暂时保存任务的工作队列。
- RejectedExecutionHandler: 当 ThreadPoolExecutor 已经关闭或 ThreadPoolExecutor 已经饱和(达到最大线程池大小且工作队列已满),executre() 方法将要调用的 Handler。
通过 Executor 框架的工具类Executors,可以创建3种类型的 ThreadPoolExecutor:
- FixedThreadPool
- SingleThreadExecutor
- CachedThreadPool
2.2 FixedThreadPool 详解
2.2.1 特点
可重用固定线程数的线程池
2.2.2 应用场景
适用于为了满足资源管理需求而需要限制当前线程数量的应用场景,适用于负载比较重的服务器。
2.2.3 构造方法实现
- corePoolSize 和 maximumPoolSize 都被设置为创建 FixedThreadPool时指定的 nThreads。
- 把 keepAliveTime 设置为 0L,使多余的空闲线程会被立即终止。
当线程池中的线程数大于 corePoolSize 时, keepAliveTime 为多余的空闲线程等待新任务的最长时间,超过这个时间后多余的线程将被终止。
public static ExecutorService newFixedThreadPool(int nThreads){
return new THreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
2.2.4 运行原理
- 如果当前线程数少于 corePoolSize, 则创建新线程来执行任务。
- 在线程池完成预热后(当前运行的线程数等于 corePoolSize),将任务加入 LinkedBlockingQueue。
- 线程执行完 1 中的任务后,会在循环中反复从 LinkedBlockingQueue 获取任务来执行。
FixedThreadPool 使用无界队列 LinkedBlockingQueue 作为线程池的工作队列(容量为 Integer.MAX_VALUE),有如下影响:
- 线程数达到 corePoolSize 后, 新任务将在无界队列中等待,故线程数不会超过 corePoolSize。
- 由于 1, maximumPoolSize 将是一个无效参数。
- 由于 1 和 2,keepAliveTime 将是一个无效参数。
- 由于使用无界队列,运行中的(未关闭的) FixedThreadPool 不会拒绝任务(不会调用 RejectedExecutionHandler.rejectedExecution 方法)。
2.3 SingleThreadExecutor 详解
2.3.1 特点
使用单个 worker 线程的 Executor。
2.3.2 应用场景
适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程活动的场景。
2.3.3 构造方法实现
- corePoolSize 和 maximumPoolSize 被设置为 1。
- 其他参数和 FixedThreadPool 相同。
public static ExecutorService newSingleThreadExecutor(){
return new FinalizableDelegatedExecutorService(
new ThreadPoolExecutor(1,1,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())
);
}
2.3.4 运行原理
- 如果当前运行的线程数少于 corePoolSize (即线程池中无运行的线程),则创建一个新线程来执行任务。
- 线程池完成预热后(线程池中有一个运行的线程),将任务加入 LinkedBlockingQueue。
- 线程执行完1中的任务后,会在一个无限循环中反复从 LinkedBlockingQueue 获取任务来执行。
2.4 CachedThreadPool 详解
2.4.1 特点
会根据需要创建新线程的线程池。
2.4.2 应用场景
大小无界的线程池,适用于执行很多的短期异步任务的小程序,或者负载较轻的服务器。
2.4.3 构造方法实现
- corePoolSize 设置为0,maximumnPoolSize 设置为 Integer.MAX_VALUE,即maximumPool是无界的。
- keepAliveTime 设置为60秒。
- 使用没有容量的 SynchronousQueue 作为工作队列。
主线程提交任务的速度高于 maximumPool 中线程处理任务的速度时, 会不断地创建新线程。
极端情况下,会因为创建过多线程而耗尽 CPU 和内存资源。
public static ExecutorService newCachedThreadPool(){
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
}
2.4.4 运行原理
- 先执行 SynchronousQueue.offer(Runnable task)。如果当前 maximumPool 中有空线程正在执行 SynchronousQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS), 那么主线程执行 offer 操作与空闲线程执行的 poll 操作配对成功,主线程把任务交给空闲线程执行, execute() 方法执行完成;否则执行步骤 2;
- 创建一个新线程执行任务, execute() 方法执行完成。
- 步骤2中创建的线程将任务执行完后,会执行 poll 操作,让空闲线程最多在 SynchronousQueue 中等待60秒。如果这期间主线程提交了一个新任务,那么该空闲线程将执行该新任务;否则该空闲线程将终止。
3. ScheduledThreadPoolExecutor 详解
3.1 特点
继承自 ThreadPoolExecutor,主要用来在给定的延迟之后运行任务,或定期执行任务。
功能类似 Timer,但更强大、灵活。
- Timer 对应的是单个后台线程;
- ScheduledThreadPoolExecutor 可以在构造函数中指定多个对应的后台线程数。
3.2 应用场景
- ScheduledThreadPoolExecutor 适用于需要多个后台线程执行周期任务,同时为了满足资源管理需求而需要限制后台线程的数量的应用场景。
- SingleThreadScheduledExecutor适用于需要单个后台线程执行周期任务,同时需要保证顺序地执行各个任务的应用场景。
3.3 运行机制
- 当调用 scheduleAtFixedRate() 方法或者 scheduleWithFixedDelay() 方法时,会向 DelayQueue 添加一个实现了 RunnableScheduledFutur 接口的 ScheduledFutureTask。
- 线程池中的线程从 DelayQueue 中获取 ScheduledFutureTask,然后执行任务。
ScheduledThreadPoolExecutor 为了实现周期性的执行任务,对 ThreadPoolExecutor 做了如下修改:
- 使用无界队列 DelayQueue 作为任务队列(故 maximumPoolSize 在此是一个无效参数)。
- 获取任务的方式不同。
- 执行周期任务后 ,增加了额外的处理。
3.4 实现
3.4.1 结构
ScheduledFutureTask 主要包含3个成员变量:
- time: long 型,表示该任务将要被执行的具体时间。
- sequenceNumber: 表示该任务被添加到 ScheduledThreadPoolExecutor 中的序号。
- period: 表示任务执行的间隔周期。
DelayQueue 封装了一个 PriorityQueue,对队列中的 ScheduledFutureTask 进行排序,time 小的排在前面(时间早的任务将被先执行)。如果两个任务的 time 相同,就比较 sequenceNumber,小的排在前面(时间相同的任务,先提交的先执行)。
3.4.2 执行过程
- 线程1从 DelayQueue 中获取已到期(time大于等于当前时间)的ScheduledFutureTask(DelayQueue.take())。
- 线程1执行这个 ScheduledFutureTask。
- 线程1修改 ScheduledFutureTask 的time 变量为下次将要被执行的时间。
- 线程1把这个修改 time 之后的 ScheduledFutureTask 放回 DelayQueue 中(DelayQueue.add())。
3.4.3 获取任务 DelayQueue.take()
- 获取Lock。
- 获取周期任务。
2.1 如果 PriorityQueue 为空,当前线程到 Condition 中等待;否则执行下面的 2.2。
2.2 如果 PriorityQueue 的头元素 time 比当前时间大,到 Condition 中等待到 time 时间;否则执行下面的 2.3。
2.3 获取 PriorityQueue 的头元素(2.3.1);如果 PriorityQueue 不为空,则唤醒在 Condition 中等待的所有线程(2.3.2)。 - 释放 Lock。
ScheduledThreadPoolExecutor 在一个循环中执行步骤2,直到线程从 PriorityQueue 获取到一个元素之后,才会退出无限循环。
3.4.4 添加任务 DelayQueue.add()
- 获取 Lock。
- 添加任务。
2.1 向 PriorityQueue 添加任务。
2.2 如果在 2.1 中添加的任务时 PriorityQueue 的头元素,唤醒在 Condition 中等待的所有线程。 - 释放 Lock。
4. FutureTask 详解
4.1 简介
4.1.1 FutureTask.run()
FutureTask 同时实现了 Future 接口和 Runnable 接口。
FutureTask 有如下3种状态:
- 未启动: run() 方法未执行之前。
- 已启动: run() 方法被执行的过程种。
- 已完成: run() 方法执行完后正常结束,或被取消(FutureTask.cancel()),或执行 run() 方法时抛出异常而异常结束。
4.1.2 FutureTask.get()
- 当 FutureTask 处于未启动或已启动状态时,执行 get() 方法将导致调用线程阻塞;
- 当 FutureTask 处于已完成状态时,执行 get() 方法将导致调用线程立即返回结果或抛出异常。
4.1.3 FutureTask.cancel(…)
- 当 FutureTask 处于未启动状态时,执行 cancel() 方法将导致此任务永远不会被执行;
- 当 FutureTask 处于已启动状态时,
- 执行 cancel(true) 方法将以中断执行此任务线程的方式来试图停止任务;
- 执行 cancel(false) 方法将不会对正在执行此任务的线程产生影响;
- 当 FutureTask 处于已完成状态时,执行 cancel(…) 将返回 false。
4.2 FutureTask 的使用
- 交给 Executor 执行;
- 通过 ExecutorService.submit(…) 方法返回一个 FutureTask,然后执行 FutureTask.get() 方法 或 FutureTask(…) 方法;
- 也可以单独使用 FutureTask。
4.3 FutureTask 的实现
4.3.1 内部实现
基于 AbstractQueuedSynchronizer(AQS)实现。
每一个基于同步框架 AQS 实现的同步器都会包含两种类型的操作:
- 至少一个 acquire 操作。该操作阻塞调用线程,直到 AQS 的状态允许这个线程继续执行。FutureTask 的 acquire 操作为 get() 方法。
- 至少一个 release 操作。该操作会改变 AQS 的状态,改变后的状态可允许一个或多个阻塞线程被解除阻塞。FutureTask 的 realse 操作包括 run() 方法和 cancel(…) 方法。
FutureTask 声明了一个内部是由的继承自 AQS 的子类 Sync。对 FutureTask 所有公有方法的调用都会委托这个内部子类。
Sync 实现了 AQS 的 tryAcquireShared(int) 和 tryReleaseShared(int) 方法,来检查和更新同步状态。
4.3.2 FutureTask.get() 执行过程
- 调用 AQS.acquireSharedInterruptibly(int arg) 方法,先回调之类 Sync 种实现的 tryAcquireShared() 方法来判断 acquire 操作是否可以成功。(成功判定:state 为执行完成状态 RAN 或已取消状态 CANCELLED,且 runner 不为null)
- 如果成功则 get() 方法立即返回,如果失败则到线程等待队列中去等待其他线程执行 release 操作。
- 其他线程执行 release 操作唤醒当前线程后,当前线程再次 tryAcquireShared() 将返回 1,当前线程间离开线程等待队列并唤醒它的后继线程(级联唤醒)。
- 最后返回 FutureTask.done()。
4.3.3 FutureTask.run() 执行过程
- 执行在构造函数中指定的任务(Callable.call())。
- 以原子方式更新同步状态(调用 AQS.compareAndSetStateint(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),然后唤醒线程等待队列中的第一个线程。
- 调用 FutureTask.done()。
4.3.4 级联唤醒
- 当执行 FutureTask.get() 时,如果不是处于执行完成状态 RAN 或已取消状态 CANCELLED,当前执行线程将到 AQS 的线程等待队列中等待。
- 当某个线程执行 FutureTask.run() 方法或 FutureTask.cancel(…) 方法时,会唤醒线程等待队列的第一个线程。
- 第一个线程唤醒后,会先把自己从队列中删除,然后唤醒它的后继线程,以此类推,直到所有线程都被级联唤醒并从 get() 方法返回。