第10章 Executor框架
Java线程既是工作单元也是执行机制,JDK5开始把工作单元和执行机制分离开来。工作单元包括Runnable和Callable,执行机制由Executor框架提供
10.1 Executor框架简介
10.1.1 Executor框架的两级调度模型
在Hotspot VM的内存模型中,Java线程被一对一映射成本地操作系统线程。Java线程启动时会创建一个本地线程,Java线程终止时,对应的本地线程会被回收。
在上层,Java多线程程序通常把应用分解成若干个任务,通过用户级的调度器——Executor框架,将这些任务映射为若干数量的线程。
在底层,操作系统内核将这些线程映射到硬件处理器上。
10.1.2 Executor框架的结构与成员
Executor框架主要由3大部分组成:
任务:被执行任务需要实现Runnable接口和Callable接口
任务的执行:核心接口Executor以及继承自Executor接口的ExecutorService接口,两个关键实现类:ThreadPoolExecutor和ScheduledThreadPoolExecutor
任务执行的接口:Future接口和实现Future接口的FutureTask类
Executor框架的使用
1.创建实现Runnable接口或者Callable接口的任务对象
2.将任务直接交给Executor执行:ExecutorService.execute(runnable),或者将任务提交给Executor:ExecutorService.submit(runnable),
3.将任务执行结果交给实现Future接口的对象(FutureTask)
4.通过FutureTask.get()方法来等待任务执行,或者通过Future.cancle()方法来取消任务执行
Executor框架的成员
1.ThreadPoolExecutor 通常使用工厂类Executors创建
FixedThreadPool 固定线程数,适用于负载比较重的服务器。
SingleThreadPoolExecutor 单个线程,适用于需要保证顺序执行各个任务,并且在同一时间点不会有多个线程是活动状态的场景
CachedThreadPool 可以根据需要创建新的线程,是大小无界的线程池,适用于执行很多的短期异步任务的小程序,或负载比较轻的服务器
2.ScheduledThreadPoolExecutor 通常使用工厂类Executors创建
ScheduledThreadPoolExecutor 包含若干个线程,适用于多个后台线程执行周期任务同时满足资源管理需求从而限制后台线程数量的场景
SingleScheduledThreadPoolExecutor 只包含单个线程,适用于后台一个线程执行周期任务,同时需要顺序执行各个任务的场景
3.Future接口和实现类FutureTask 异步计算的结果
4.Runnable接口和Callable接口 任务对象需要实现的接口,Runnable不会返回结果,Callable可以返回结果
10.2 ThreadPoolExecutor详解
Executor框架最核心的类,它是线程池的实现类,由4个组件组成
corePool:核心线程池大小
maximum:最大线程数
BlockingQueue:用来保存工作任务的队列
RejectedExecutionHandler:当线程池关闭或者线程池已满(线程数量达到最大线程数且工作队列已满)时调用的handler
通过Executor框架的工具类Executors,可以创建3中ThreadPoolExecutor
FixedThreadPool
SingleThreadPool
CachedThreadPool
10.2.1 FixedThreadPool详解
可重用固定线程数的线程池,FixedThreadPool的源代码实现:
public static ExecutorService newFixedThreadPool(int nThreads){
return new ThreadPoolExecutor(nThreads,nThreads,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue());
}
核心线程池大小corePoolSize和最大线程数maxPoolSize都被设为创建FixedThreadPool时指定的参数nThreads
keepAliveTime(当线程池中的线程数大于corePoolSize时,多余的空闲线程等待新任务的时间,超过这个时间后多余的线程将被终止)设置为0L,多余的线程会被立即终止。
FixedThreadPool的execute()方法运行流程:
1.预热:当线程池中的线程数少于corePoolSize时,创建新线程来执行任务
2.完成预热后,将任务加入LinkedBlockingQueue
3.线程执行完预热时分配的任务后,循环从LinkedBlockingQueue获取任务来执行。
由于使用无界队列LinkedBlockingQueue(队列大小为Integer.MAX_VALUE),运行中的FixedThreadPool不会拒绝任务(不会调用RejectedExceptionHandler.rejectedExecution方法)
10.2.2 SingleThreadExecutor详解
使用单个worker线程的Executor,SinglerThreadExecutor源代码实现:
public static ExecutorService newSingleThreadPool(){
return new FinalizableDelegatedExecutorService(
new ThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue())
);
}
corePoolSize和maximumPoolSize都被设置为1,其他参数与FixedThreadPool相同。
10.2.3 CachedThreadPool详解
一个会根据需要创建新线程的线程池,源代码实现:
public static ExecutorService newCachedThreadPool(){
return new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.SECONDS,new SynchronousQueue());
}
corePoolSize=0,maximumPoolSize=Integer.MAX_VALUE(无界),keepAliveTime=60s,
使用没有容量的SynchronousQueue作为线程池的工作队列,
意味着:如果主线程提交任务的速度高于线程池处理任务的速度时,CachedThreadPool会不断创建新的线程,极端情况下会因为创建过多的线程耗尽CPU和内存资源。
CachedThreadPool的execute()运行流程
1.主线程通过execute()方法提交任务后,首先执行SynchronousQueue.offer(Runnable task),如果线程池中有空闲线程正在执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),那么主线程的offer与空闲线程的poll配对成功,主线程将任务交给空闲线程执行,execute方法执行完成
2.否则CachedThreadPool创建一个新的线程执行任务,execute方法执行完成
3.步骤二创建的新线程将任务执行完成后,会执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)方法,来等待新任务,若没有新任务,空闲60s后线程会被终止。
SynchronousQueue是一个没有容量的阻塞队列,每一个插入操作必须等待另一个线程的对应移除操作,反之亦然。
CachedThreadPool使用SynchronousQueue把主线程提交的任务交个空闲线程执行。
10.3 ScheduledThreadPoolExecutor详解
继承自ThreadPoolExecutor,用来在给定的延迟之后运行任务,或者定期执行任务。
功能与Timer相似,但比Timer强大,Timer对应的是单个后台线程,ScheduledThreadPoolExecutor可以在构造函数中指定多个对应的后台线程数
10.3.1 ScheduledThreadPoolExecutor的运行机制
1.添加任务 调用ScheduledThreadPoolExecutor的scheduleAtFixedRate()方法或者scheduleWithFixedDelay()方法会向ScheduledThreadPoolExecutor的DelayQueue添加一个实现了RunnableScheduledFuture接口的ScheduledFutureTask。
2.执行任务 线程池中的线程从DelayQueue中获取ScheduledFutureTask,然后执行任务
10.3.2 ScheduledThreadPoolExecutor的实现
ScheduledThreadPoolExecutor会把待调度的任务(ScheduledFutureTask)放到一个DelayQueue中。
ScheduledFutureTask主要包含3个成员变量:
private long time 任务执行的具体时间
private final long sequenceNumber 任务序号
private final period 任务执行的周期
DelayQueue封装了一个PriorityQueue,这个PriorityQueue会对队列中的ScheduledFutureTask进行排序。
排序规则:timer(小的排前面,时间早的任务先执行)——>sequenceNumber(先提交的任务先执行)
ScheduledThreadPoolExecutor中线程执行周期任务的过程:
1.获取到期任务 DelayQueue.take()
获取Lock
获取周期任务
释放Lock
2.执行这个ScheduledFutureTask
3.修改time(任务的下一次执行时间)
4.将任务放回Delayqueue DelayQueue.add()
获取Lock
添加任务
释放Lock
10.4 FutureTask详解
10.4.1 FutureTask简介
FutureTask除了实现Future接口外,还实现了Runnable接口,因此FutureTask可以交给Executor执行,也可以由调用线程执行(FutureTask.run())
FutureTask的3中状态:
未启动 run方法未被执行之前
已启动 run方法被执行过程中
已完成 run方法执行完成或者被取消(FutureTask.cancle()),或者执行run方法时抛出异常而异常结束
get()
当FutureTask处于未启动或已启动时,调用FutureTask.get()方法将导致调用线程阻塞,
当FutureTask处于已完成状态时导致调用线程立即返回结果或抛出异常
cancle()
当FutureTask处于未启动时,执行FutureTask.cancle()方法将导致此任务永远不会被执行。
当FutureTask处于已启动时,执行FutureTask.cancle(true)方法将以中断执行此任务线程的方式来试图停止任务,执行FutureTask.calcle(false),将不会对正在执行此任务的线程产生影响(让正在执行的任务运行完成)。
当FutureTask处于已完成状态时,执行FutureTask.cancle(...)将返回false。
10.4.2 FutureTask的使用
可以把FutureTask交给Executor执行,也可以通过ExecutorService.submit(…)方法返回一个FutureTask。
当一个线程需要等待另一个线程把某个任务执行完后才能继续执行,此时可以使用FutureTask。
10.4.3 FutureTask的实现
FutureTask的实现基于AbstractQueuedSynchronizer(AQS)。java.util.concurrent包中很多阻塞类都是基于AQS来实现的。
AQS是一个同步框架,它提供通用机制来原子性管理同步状态、阻塞、和唤醒线程,以及维护被阻塞线程的队列。
JDK6中AQS被广泛使用,基于AQS的同步器包括:ReentrantLock、Semaphore、ReentrantReadWriteLock、CountDownLatch和FutureTask。
每一个基于AQS实现的同步器都会包含两种类型的操作:
acquire 阻塞调用线程,除非/直到AQS的状态允许这个线程继续执行。FutureTask的get()方法
release 改变AQS的状态,改变状态后可允许一个或多个阻塞线程被解除。FutureTask的run()方法和cancle()方法。
基于“复合优先于继承”的原则,FutureTask声明了一个内部私有的继承与AQS的子类Sync,对FutureTask所有公有方法的调用都会委托给这个内部子类。——————JDK8中已经没有这个子类了