Java的线程既是工作单元,也是执行机制
从JDK 5开始,把工作单元与执行机制分离开来:
- 工作单元包括Runnable和Callable
- 执行机制由Executor框架提供
1.Executor框架简介
1.1 两级调度模型
-
Java线程被一对一映射为本地操作系统线程
-
在上层,Java多线程程序通常把应用分解为若干个任务,然后使用用户级的调度器(Executor框架)将这些任务映射为固定数量的线程;在底层,操作系统内核将这些线程映射到硬件处理器上
1.2 结构与成员
- 该框架主要由3大部分组成如下:
- 任务:包括被执行任务需要实现的接口(即Runnable或Callable接口)
- 任务的执行:包括任务执行机制的核心接口Executor以及继承自Executor的ExecutorService接口
- 异步计算的结果:包括接口Future和实现Future接口的FutureTask类
-
框架主要成员:
-
ThreadPoolExecutor:使用工厂类Executors来创建,可创建以下三种类型
-
FixedThreadPool:适用于为了满足资源管理的需求而需要限制当前线程数量的应用场景,它适用于负载比较重的服务器
-
SingleThreadExecutor:适用于需要保证顺序地执行各个任务且在任意时间点不会有多个线程是活动的应用场景
-
CachedThreadPool:大小无界的线程池,适用于执行很多的短期异步任务的小程序或是负载较轻的服务器
-
-
-
ScheduledThreadPoolExecutor:使用工厂类Executors来创建,可创建以下两种类型
-
ScheduledThreadPoolExecutor:适用于需要多个后台线程执行周期任务,同时为了满足资源管理的需求而需要限制后台线程的数量的应用场景
-
SingleThreadScheduledExecutor:适用于需要单个后台线程执行周期任务,同时需要保证顺序地执行各个任务的应用场景
-
Future接口:Future接口和实现Future接口的FutureTask类用来表示异步计算的结果(FutureTask实现了Runnable,可创建FutureTask然后交给ExecutorService执行)
-
-
Runnable和Callable接口:
- 这两个接口的实现类可被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行
- Runnable不会返回结果,而Callable可以返回结果
2.ThreadPoolExecutor详解
- 它线程池的实现类,主要由下列4个组件构成:
- corePool:核心线程池的大小
- maximumPool:最大线程池的大小
- BlockingQueue:暂时保存任务的工作队列
- RejectedExecutionHandler:拒绝策略
- 可以创建3种类型的ThreadPoolExecutor:
- FixedThreadPool
- SingleThreadExecutor
- CachedThreadPool
2.1 FixedThreadPool详解
被称为可重用固定线程数的线程池,corePoolSize和maximumPoolSize都被设置为创建FixedThreadPool时指定的参数nThreads
-
如果当前运行的线程数少于corePoolSize,则创建新线程来执行任务
-
在线程池完成***预热(当前运行的线程数等于corePoolSize)***之后,将任务加入LinkedBlockingQueue,使用该无界队列带来的影响:
- 线程池中的线程数不会超过corePoolSize(因为再多任务也可放入队列)
- maximumPoolSize(因为无界)和keepAliveTime(因为不会有非核心线程创建)将是无效参数
- 不会拒绝任务
-
线程执行完任务后,会在循环中反复从LinkedBlockingQueue获取任务来执行
2.2 SingleThreadExecutor详解
使用单个worker线程的Executor,corePoolSize和maximumPoolSize被设置为1
-
如果当前运行的线程数少于corePoolSize(即线程池中无运行的线程),则创建一个新线
程来执行任务 -
在线程池完成预热(当前线程池中有一个运行的线程)之后,将任务加入LinkedBlockingQueue,,使用该无界队列带来的影响参考上一小节
-
线程执行完任务后,会在循环中反复从LinkedBlockingQueue获取任务来执行
2.3 CachedThreadPool详解
会根据需要创建新线程的线程池,corePoolSize被设置为0(即corePool为空),maximumPoolSize被设置为Integer.MAX_VALUE(即线程池无界)
下述流程和上面两个线程池的区别主要是因为CachedThreadPool使用SynchronousQueue,该队列是没有容量的阻塞队列(即每个插入操作必须等待另一个线程的对应移除操作)
-
首先执行
SynchronousQueue.offer
,如果当前maximumPool中有空闲线程正在执行SynchronousQueue.poll
,则主线程把任务交给空闲线程执行 -
如果初始maximumPool为空或maximumPool中当前没有空闲线程时,将没有线程执行
SynchronousQueue.poll
,此时CachedThreadPool会创建一个新线程执行任务 -
新创建的线程将任务执行完后,会执行
SynchronousQueue.poll
(该操作会让空闲线程最多等待60秒钟),如果60秒钟内主线程提交了一个新任务,则这个空闲线程将执行新任务;否则该空闲线程将终止(长时间保持空闲的CachedThreadPool不会使用任何资源)
3.ScheduledThreadPoolExecutor详解
-
主要用来在给定的延迟之后运行任务或定期执行任务
-
Timer对应的是单个后台线程,ScheduledThreadPoolExecutor可以在构造函数中指定多个对应的后台线程数
3.1 运行机制
- 为了实现周期性的执行任务,对ThreadPoolExecutor做了如下的修改:
- 使用DelayQueue(无界队列,具体影响参考2.1 FixedThreadPool详解)作为任务队列,该队列封装PriorityQueue,意味着可对ScheduledFutureTask进行排序(根据time和sequenceNumber进行比较)
- 获取任务的方式不同
- 执行周期任务后,增加了额外的处理
3.2 实现
- ScheduledThreadPoolExecutor会把待调度的任务(即ScheduledFutureTask)放到一个DelayQueue中,而ScheduledFutureTask主要包含3个成员变量:
- time:表示该任务将要被执行的具体时间
- sequenceNumber:表示该任务被添加到ScheduledThreadPoolExecutor的序号
- period:表示该任务执行的间隔周期
- ScheduledThreadPoolExecutor中的线程1执行某个周期任务的4个步骤:
- 线程1从DelayQueue中获取已到期的ScheduledFutureTask(即time大于等于当前时间的ScheduledFutureTask)
- 线程1执行这个ScheduledFutureTask后,将该Task的time变量为下次将要被执行的时间
- 修改后将该Task放回DelayQueue中
4.FutureTask详解
-
Future接口和实现Future接口的FutureTask类代表异步计算的结果
-
FutureTask除了实现Future接口外,还实现了Runnable接口(因此可交给Executor执行,也可以由调用线程直接执行,即执行
FutureTask.run
) -
根据
FutureTask.run
被执行的时机,FutureTask可处于下面3种状态:- 未启动:方法还没有被执行之前,FutureTask处于未启动状态
- 已启动:方法被执行的过程中,FutureTask处于已启动状态
- 已完成:方法执行完后正常结束或、取消(即执行
FutureTask.cancel
)或抛异常,FutureTask处于已完成状态
-
FutureTask中get方法和cancel方法的执行如下:
4.1 使用
- 当一个线程需要等待另一个线程把某个任务执行完后它才能继续执行,可使用FutureTask
4.2 实现
- FutureTask的实现基于AbstractQueuedSynchronizer(AQS是同步框架,它提供通用机制来原子性管理同步状态、阻塞和唤醒线程,以及维护被阻塞线程的队列)