一、什么是Executor框架?
我们知道线程池就是线程的集合,线程池集中管理线程,以实现线程的重用,降低资源消耗,提高响应速度等。线程用于执行异步任务,单个的线程既是工作单元也是执行机制,从JDK1.5开始,为了把工作单元与执行机制分离开,Executor框架诞生了,他是一个用于统一创建与运行的接口。Executor框架实现的就是线程池的功能。
二、Executor框架结构图解
1、Executor框架包括3大部分:
- 任务。也就是工作单元,包括被执行任务需要实现的接口:Runnable接口或者Callable接口;
- 任务的执行。也就是把任务分派给多个线程的执行机制,包括Executor接口及继承自Executor接口的ExecutorService接口。
- 异步计算的结果。包括Future接口及实现了Future接口的FutureTask类。
Executor框架的成员及其关系可以用一下的关系图表示:
这些类和接口简介:
- Executor:是一个接口,它是Executor框架的基础,它将任务的提交与任务的执行分离开来。
- ThreadPoolExecutor:是线程池的核心实现类,用来执行被提交的任务。
- ScheduledThreadPoolExecutor:是一个实现类,可以在给定的延迟后运行命令,或者定期执行命令。ScheduledThreadPoolExecutor比Timer更灵活,功能更强大。
- Future接口和实现Future接口的FutureTask类,代表异步计算的结果。
- Runnable接口和Callable接口的实现类,都可以被ThreadPoolExecutor或Scheduled-ThreadPoolExecutor执行。
使用步骤:
1、创建Runnable并重写run()方法或者Callable对象并重写call()方法:
class callableTest implements Callable<String >{
@Override
public String call() {
try{
String a = "return String";
return a;
}
catch(Exception e){
e.printStackTrace();
return "exception";
}
}
}
2、创建Executor接口的实现类ThreadPoolExecutor类或者ScheduledThreadPoolExecutor类的对象,然后调用其execute()方法或者submit()方法把工作任务添加到线程中,如果有返回值则返回Future对象。其中Callable对象有返回值,因此使用submit()方法;而Runnable可以使用execute()方法,此外还可以使用submit()方法,只要使用callable(Runnable task)或者callable(Runnable task, Object result)方法把Runnable对象包装起来就可以,使用callable(Runnable task)方法返回的null,使用callable(Runnable task, Object result)方法返回result。
ThreadPoolExecutor tpe = new ThreadPoolExecutor(5, 10,
100, MILLISECONDS, new ArrayBlockingQueue<Runnable>(5));
Future<String> future = tpe.submit(new callableTest());
3、调用Future对象的get()方法后的返回值,或者调用Future对象的cancel()方法取消当前线程的执行。最后关闭线程池
try{
System.out.println(future.get());
}
catch(Exception e){
e.printStackTrace();
}
finally{
tpe.shutdown();
}
2、Executor框架成员
ThreadPoolExecutor实现类、ScheduledThreadPoolExecutor实现类、Future接口、Runnable和Callable接口、Executors工厂类
三、ThreadPoolExecutor详解
ThreadPoolExecutor是Executor接口的一个重要的实现类,是线程池的具体实现,用来执行被提交的任务。
主要由下面4个组件构成:
- corePool:核心线程池的大小。
- maximumPool:最大线程池的大小。
- BlockingQueue:用来暂时保存任务的工作队列。
- RejectedExecutionHandler:当ThreadPoolExecutor已经关闭或已经饱和时(达到了最大线程池且工作队列已满),execute()方法将要调用的Handler
ThreadPoolExecutor tpe = new ThreadPoolExecutor(
int corePoolSize,int maximumPoolSize,
long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue);
ThreadPoolExecutor的三种类型
通过Executor框架的工具类Executors,可以创建(Executors.newXXX)3种类型的ThreadPoolExecutor:FixedThreadPool、SingleThreadExecutor、CachedThreadPool。
1、FixedThreadPool :可重用固定线程数的线程池
(适用于为了满足资源管理的需求,而需要适当限制当前线程数量的情景,适用于负载比较重的服务器。)
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
FixedThreadPool的executor()方法的运行示意图:
- 如果当前运行的线程数少于corePoolSize,则创建新线程来执行任务。
- 在线程池完成预热之后(当前运行的线程数等于corePoolSize),将任务加入LinkedBlockingQueue。
- 当线程执行完1中的任务后,会在循环中反复从LinkedBlockingQueue获取任务来执行。
【注】FixedThreadPool使用无界队列LinkedBlockingQueue作为线程池的工作队列。使用无界队列会对线程池带来如下影响:
- 当线程池中的线程数达到corePoolSize后,新任务将在无界队列中等待,因此线程池中的线程数不会超过corePoolSize。
- 使用无界队列时maximumPoolSize、keepAliveTime将是无效参数。
- 由于使用无界队列,运行中的FixedThreadPool(未执行方法shutdown()或shutdownNow())不会拒接任务。(不会调用RejectedExecutionHandler.rejectedExecution方法)
2、SingleThreadExecutor:只会创建一个线程执行任务
(适用于需要保证顺序执行各个任务;并且在任意时间点,没有多线程活动的场景。)
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
SingleThreadExecutor的executor()方法的运行示意图:
- 如果当前运行的线程数少于corePoolSize(<1,即线程池中无运行的线程),则创建新线程来执行任务。
- 在线程池完成预热之后(当前线程池中有一个运行的线程),将任务加入LinkedBlockingQueue。
- 当线程执行完1中的任务后,会在循环中反复从LinkedBlockingQueue获取任务来执行。
【注】SingleThreadExecutorl也使用无界队列LinkedBlockingQueue作为工作队列,对线程池带来的影响与FixedThreadPool相同。
3、CachedThreadPool :是一个会根据需要创建线程的线程池。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
(大小无界,适用于执行很多的短期异步任务的小程序,或负载较轻的服务器)
【注】CachedThreadPool使用没有容量的SynchronousQueue作为线程池的工作队列,但CachedThreadPool的maximumPool是无界的。这意味着,如果主线程提交任务的速度高于maximumPool中线程处理任务的速度时,CachedThreadPool会不断创建新线程。极端情况下,CachedThreadPool会因为创建过多线程而耗尽CPU和内存资源。
- 它是一个可以无限扩大的线程池;
- 它比较适合处理执行时间比较小的任务;
- corePoolSize为0,maximumPoolSize为无限大,意味着线程数量可以无限大;
- keepAliveTime为60S,意味着线程空闲时间超过60S就会被杀死;
- 采用SynchronousQueue装等待的任务,这个阻塞队列没有存储空间,这意味着只要有请求到来,就必须要找到一条工作线程处理他,如果当前没有空闲的线程,那么就会再创建一条新的线程。
CachedThreadPool的工作流程大概如下:
- 首先执行SynchronousQueue.offer(Runnable task)。如果当前maximumPool中有空闲线程正在执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),那么主线程执行offer操作与空闲线程执行的poll操作配对成功,主线程把任务交给空闲线程执行,executor()方法执行完成;否则执行下面的步骤2.
- 当初始maximumPool为空,或者maximumPool中当前没有空闲线程时,将没有线程执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。这种情况下步骤1将失败。此时CachedThreadPool会创建一个新线程执行任务,executor方法执行完成。
- 在步骤2中创建的线程将任务执行完后,会执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。这个poll操作会让空闲线程最多在SynchronousQueue中等待60秒。如果60秒内主线程提交了一个新任务,那么这个空闲线程将执行主线程提交的新任务;否则,这个空闲线程将终止。由于空闲60秒的空闲线程会被终止,因此长时间保持空闲的CachedThreadPool不会使用任何资源。
四、ScheduledThreadPoolExecutor的详解
ScheduledThreadPoolExecutor类继承了ThreadPoolExecutor并实现了ScheduledExecutorService接口。主要用于在给定的延迟后执行任务或者定期执行任务。作用类似于java.util包下的Timer类,但是比Timer功能更强大、更灵活,因为Timer只能控制单个线程延迟或定期执行,而ScheduledThreadPoolExecutor对应的是多个线程的后台线程。
1、ScheduledThreadPoolExecutor的创建:
可以利用Executors工厂类来创建两种ScheduledThreadPoolExecutor:ScheduledThreadPoolExecutor和SingleThreadScheduledExecutor.
ScheduledThreadPoolExecutor:适用于若干个(固定)线程延时或者定期执行任务,同时为了满足资源管理的需求而需要限制后台线程数量的场景。
ScheduledExecutorService stp = Executors.newScheduledThreadPool(int threadNums);
ScheduledExecutorService stp = Executors.newScheduledThreadPool(int threadNums, ThreadFactory threadFactory);
SingleThreadScheduledExecutor:适用于需要单个线程延时或者定期的执行任务,同时需要保证各个任务顺序执行的应用场景。
ScheduledExecutorService stse = Executors.newSingleThreadScheduledExecutor(int threadNums);
ScheduledExecutorService stp = Executors.newSingleThreadScheduledExecutor(int threadNums, ThreadFactory threadFactory);
2、ScheduledThreadPoolExecutor的实现
通过查看源码,可以发现ScheduledThreadPoolExecutor的实现主要是通过把任务封装为ScheduledFutureTask来实现。ScheduledThreadPoolExecutor通过它的scheduledAtFixedTime()方法或者scheduledWithFixedDelay()方法向阻塞队列添加一个实现了RunnableScheduledFutureTask接口的ScheduledFutureTask类对象。
ScheduledFutureTask主要包括3个成员变量:
private final long sequenceNumber;
private long time;
private final long period;
sequenceNumber:序列号,用于保存任务添加到阻塞队列的顺序;
time:用于保存该任务将要被执行的具体时间;
period:周期,用于保存任务执行的间隔周期;
此外,ScheduledTreadPoolExecutor的阻塞队列是用DelayQueue实现的,可以实现元素延时delayTime后才能获取元素,在ScheduledThreadPoolExecutor中,DelayQueue内部封装了一个PriorityQueue,来对任务进行排序,首先对time排序,time小的在前,如果time一样,则sequence小的在前,也就是说如果time一样,那么先被提交的任务先执行。
因为DelayQueue是一个无界的队列,因此线程池的maximumPoolSize是无效的。ScheduledThreadPoolExecutor的工作流程大致如下:
- 主线程把调用ScheduledFutureTask对象的scheduledAtFixedRate()或者scheduledWithFixedDelay()方法把Runnable或者Callable对象包装成ScheduledFutureTask对象添加到阻塞队列中;
- 线程池中的线程1从阻塞队列中获取到期的ScheduledFutureTask任务,并执行任务;(到期的意思就是当前时间大于time)
- 执行完任务后把成员变量time改为下次要执行任务的时间,然后把新的ScheduledFutureTask任务重新放入阻塞队列中;
五、深入理解Executor框架FutureTask类
一直以来都对FutureTask这个“Future”不理解,为什么叫做“未来的任务呢”?这个“Future”体现在哪里呢?现在终于明白,FutureTask的Future就源自于它的异步工作机制,如果我们在主线程中直接写一个函数来执行任务,这是同步的任务,也就是说必须要等这个函数返回以后我们才能继续做接下的事情,但是如果这个函数返回的结果对接下来的任务并没有意义,那么我们等在这里是很浪费时间的,而FutureTask就提供了这么一个异步的返回结果的机制,当执行一个FutureTask的时候,我们可以接着做别的任务,在将来的某个时间,FutureTask任务完成后会返回FutureTask对象来包装返回的结果,只要调用这个对象的get()方法即可获取返回值。
当然多线程中继承ThreadPoolExecutor和实现Runnable也可以实现异步工作机制,可是他们没有返回值。这时可以使用FutureTask包装Runnable或者Callable对象,再使用FutureTask来执行任务。
Future接口和其唯一的实现类FutureTask类一般用于表示异步计算的结果。Future接口下提供方法来检查计算是否完成,等待其完成,并检索计算结果。 结果只能在计算完成后使用方法get进行检索,如有必要,阻塞,直到准备就绪。 取消由cancel方法执行,isCancelled方法用于检测计算是否被取消,isDone方法用于检测计算是否完成。 提供其他方法来确定任务是否正常完成或被取消。
1、FutureTask的使用
FutureTask对象可以处于一下3种状态:
- 未启动:创建了一个FutureTask对象但没有执行futureTask.run();
- 已启动:futureTask.run()方法被执行的过程中;
- 已完成:futureTask.run()正常执行结束,或者futureTask被取消(futureTask.cancel()),或者执行futureTask.run()时抛出异常而异常结束;
FutureTask执行完后结果的获取 :futureTask.get( )
- 在已启动的状态调用futureTask.get( )将导致调用线程阻塞,直到FutureTask执行完毕,然后得到返回的FutureTask对象,调用futureTask.get( )获得任务返回值;
- 在已完成状态调用futureTask.get( ),将导致调用线程立即返回(正常完成,得到FutureTask对象)或者抛出异常(被取消或者因异常而结束)
futureTask被取消:futureTask.cancel( )
1、在未启动状态调用futureTask.cancel( )会导致该任务永远不会再执行;
2、在已启动状态:
- 调用futureTask.cancel(true)会以中断的方式尝试停止任务,如果该任务不响应中断则无法停止;
- 调用futureTask.cancel(false)将不会对正在执行的线程产生影响,也就是已启动的线程会让他执行完毕;
3、在已完成状态调用futureTask.cancel( )则会返回false;
FutureTask的启动:
FutureTask实现了Future接口和Runnable接口,因此FutureTask对象的执行有两种方式:
1、交给线程池的Execute或submit方法执行;
import java.util.concurrent.*;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
class test{
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor tpe = new ThreadPoolExecutor(5, 10,100, MILLISECONDS, new ArrayBlockingQueue<Runnable>(5));
//用FutureTask包装Runnable或者Callable对象
FutureTask<String> future = new FutureTask<String>(new Callable<String>() {
@Override
public String call() {
try{
String a = "return String";
return a;
}
catch(Exception e){
e.printStackTrace();
return "exception";
}
}
});
//交给线程池的Execute或submit方法执行
tpe.submit(future);
try{
System.out.println(future.get());
}
catch(Exception e){
e.printStackTrace();
}
finally{
tpe.shutdown();
}
}
}
2、由调用线程直接执行:在调用线程中执行futureTask.run()方法;
把上述代码的 tpe.submit(future); 替换为 future.run();
2、FutureTask的实现
FutureTask是一个基于AQS同步队列实现的一个自定义同步组件,通过对同步状态state的竞争实现acquire或者release操作。
FutureTask的内部类Sync实现了AQS接口,通过对tryAcquire等抽象方法的重写和模板方法的调用来实现内部类Sync的tryAcquireShared等方法,然后聚合Sync的方法来实现FutureTask的get,cancel等方法;
FutureTask的get方法最终会调用AQS.acquireSharedInterruptibly方法,这个方法操作成功的条件是同步状态为RAN或者CANCELLED,也就是说如果这个FutureTask有线程E正在执行,那么这个FutureTask的状态是RUN,因此AQS.acquireSharedInterruptibly方法调用失败,此时调用get方法的线程被阻塞,添加到等待队列中(如下图线程D,其中A,B,C是已经被阻塞添加到等待队列中的线程)。当前面执行FutureTask的线程E执行完毕,那么以原子方式更新同步状态state的值为RAN,并执行AQS.release方法,然后唤醒等待队列中的第一个节点中的线程A,此时线程A出队列获得同步状态,并原子设置state为RUN,当线程A执行完毕,把state原子更新为RUN,然后唤醒线程B,以此类推,因此一颗看出对于一个FutureTask,同一时间只有一个线程执行这个任务。
3、FutureTask使用场景
1、FutureTask执行多任务计算的使用场景,利用FutureTask和ExecutorService,可以用多线程的方式提交计算任务,主线程继续执行其他任务,当主线程需要子线程的计算结果时,在异步获取子线程的执行结果。
public class FutureTaskForMultiCompute {
public static void main(String[] args) {
FutureTaskForMultiCompute inst=new FutureTaskForMultiCompute();
// 创建任务集合
List<FutureTask<Integer>> taskList = new ArrayList<FutureTask<Integer>>();
// 创建线程池
ExecutorService exec = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
// 传入Callable对象创建FutureTask对象
FutureTask<Integer> ft = new FutureTask<Integer>(inst.new ComputeTask(i, ""+i));
taskList.add(ft);
// 提交给线程池执行任务,也可以通过exec.invokeAll(taskList)一次性提交所有任务;
exec.submit(ft);
}
System.out.println("所有计算任务提交完毕, 主线程接着干其他事情!");
// 开始统计各计算线程计算结果
Integer totalResult = 0;
for (FutureTask<Integer> ft : taskList) {
try {
//FutureTask的get方法会自动阻塞,直到获取计算结果为止
totalResult = totalResult + ft.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
// 关闭线程池
exec.shutdown();
System.out.println("多任务计算后的总结果是:" + totalResult);
}
private class ComputeTask implements Callable<Integer> {
private Integer result = 0;
private String taskName = "";
public ComputeTask(Integer iniResult, String taskName){
result = iniResult;
this.taskName = taskName;
System.out.println("生成子线程计算任务: "+taskName);
}
public String getTaskName(){
return this.taskName;
}
@Override
public Integer call() throws Exception {
// TODO Auto-generated method stub
for (int i = 0; i < 100; i++) {
result =+ i;
}
// 休眠5秒钟,观察主线程行为,预期的结果是主线程会继续执行,到要取得FutureTask的结果是等待直至完成。
Thread.sleep(5000);
System.out.println("子线程计算任务: "+taskName+" 执行完成!");
return result;
}
}
}
2、FutureTask在高并发环境下确保任务只执行一次