Executor框架底层:Executor、ExecutorService、AbstractExecutorService 和 ScheduledExecutorService

1、什么是Executor框架?

我们知道线程池就是线程的集合,线程池集中管理线程,以实现线程的重用,降低资源消耗,提高响应速度等。线程用于执行异步任务,单个的线程既是工作单元也是执行机制,从JDK1.5开始,为了把工作单元与执行机制分离开,Executor框架诞生了,他是一个用于统一创建与运行的接口。Executor框架实现的就是线程池的功能。
 

2、Executor框架结构图解

2.1、Executor框架包括3大部分

(1)任务。也就是工作单元,包括被执行任务需要实现的接口:Runnable接口或者Callable接口

(2)任务的执行。也就是把任务分派给多个线程的执行机制,包括Executor接口及继承自Executor接口的ExecutorService接口。

(3)异步计算的结果。包括Future接口及实现了Future接口的FutureTask类

Executor框架的成员及其关系可以用一下的关系图表示:

2.2、Executor框架的使用示意图

2.3、使用步骤

(1)创建Runnable并重写run()方法或者Callable对象并重写call()方法:

(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。
(3)调用Future对象的get()方法后的返回值,或者调用Future对象的cancel()方法取消当前线程的执行。最后关闭线程池

 

3、Executor框架成员

 

4、Executor 接口

4.1、Executor 接口简介

该接口提供了一种优雅的方式去解耦任务处理机制中的任务提交任务如何运行(也包含线程的使用,调度)

4.2、继承关系

4.2、源码

public interface Executor {

    // 执行任务
    void execute(Runnable command);
}

 

5、ExecutorService 接口

5.1、ExecutorService 接口简介

ExecuteService代表的是Executors创建的线程池
          submit提交的是Callable方法,返回Future,说明submit是有返回值的
          execute执行的是Runnable方法,没有返回值

所以submit和execute的区别是提交的方法和是否有返回值

关于 Callable 、Runnable 和 Future、FutureTask ,具体见:线程池系列 之 FutureTask 及其底层实现

Executor 提供了管理终止的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。

可以关闭 ExecutorService,这将导致其拒绝新任务。提供两个方法来关闭 ExecutorService。shutdown() 方法在终止前允许执行以前提交的任务,而 shutdownNow() 方法阻止等待任务启动并试图停止当前正在执行的任务。在终止时,执行程序没有任务在执行,也没有任务在等待执行,并且无法提交新任务。应该关闭未使用的 ExecutorService 以允许回收其资源。

通过创建并返回一个可用于取消执行和/或等待完成的 Future,方法 submit 扩展了基本方法 Executor.execute(java.lang.Runnable)。方法 invokeAny 和 invokeAll 是批量执行的最常用形式,它们执行任务 collection,然后等待至少一个,或全部任务完成(可使用 ExecutorCompletionService 类来编写这些方法的自定义变体)。

Executors 类提供了用于此包中所提供的执行程序服务的工厂方法

5.2、源码

public interface ExecutorService extends Executor {
    ......
}

submit 

// 提交一个需要返回结果的任务去执行,返回一个有结果的消息体,只有成功执行后,才会返回结果
<T> Future<T> submit(Callable<T> task);

// 提交一个不需要(!!!)返回结果的任务去执行,返回一个有结果的消息体,只有成功执行后,才会返回结果
Future<?> submit(Runnable task);

// 只有当任务成功被执行后,才会返回给定的结果
<T> Future<T> submit(Runnable task, T result);

invokeAll 

// 将集合里面的方法批量执行,不管有没有执行完,都返回 原始的FuturnTask列表(完成还是取消,FuturnTask的状态可以获取)
// 结果可以通过 FuturnTask.get() 获取

<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;

<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
                                                    throws InterruptedException;

invokeAny 

// 提交一批任务信息,当其中一个成功的执行,没有返回异常的时候,就返回结果
<T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException;

 <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
                                throws InterruptedException, ExecutionException, TimeoutException;

shutdown 系列

// 在之前提交的,需要被执行的任务中,有序的进行关闭操作,并且此时不会再接受新的任务
// 如果此时所有的任务已经关闭的话,那么就不会起到什么效果,因为已经没有任务可关闭了
// 重点:不会马上关闭!!!
void shutdown();

// 企图关闭所有正在执行的任务,并且中断正在等待要执行的任务,返回一个包含正在等待的任务的列表
// 重点:立刻关闭!!!
List<Runnable> shutdownNow();

// 只有当所有的任务都成功执行,否则会一直处于阻塞状态,只有当一下情况发生时,才会中断阻塞
// 例如收到一个关闭的请求,或者超时发生、或者当前的线程被中断后
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;

判断方法

// 如果线程已经关闭了,就返回true
boolean isShutdown();

// 如果所有的线程任务已经关闭了,就返回true
boolean isTerminated();

 

6、AbstractExecutorService 抽象类

6.1、AbstractExecutorService 抽象类简介

AbstractExecutorService是一个实现ExecutorService接口的非常重要的抽象类,它提供了ExecutorService接口的默认实现。一般情况下,我们可直接继承AbstractExecutorService来实现自己的任务执行器,AbstractExecutorService已经实现ExecutorService接口中的大部分抽象方法,我们自己主要去实现execute方法,此抽象类大降低了编写自定义的任务执行器的难度。我们常用的线程池执行器ThreadPoolExecutor和ForkJoin框架的执行器ForkJoinPool都是直接继承抽象类AbstractExecutorService。

AbstractExecutorService使用newTaskFor返回的RunnableFuture来实现submit,invokeAny和invokeAll方法,该方法默认为此程序包中提供的FutureTask类(FutureTask在线程池系列 之 FutureTask 及其底层实现做过详细说明,这里不再赘述)。 submit系列方法将创建一个关联的RunnableFuture,该关联的RunnableFuture将被执行并返回, 子类可以重写newTaskFor方法以返回RunTask以外的RunnableFuture实现。

 

6.2、源码分析

public abstract class AbstractExecutorService implements ExecutorService {
    ......
}

(1)封装任务系列

// 封装 Callable类型的task
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
    return new FutureTask<T>(callable);
}
// 封装 Runnable类型的task
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
    return new FutureTask<T>(runnable, value);
}

关于 FutureTask 具体见:线程池系列 之 FutureTask 及其底层实现

 

(2)submit 系列

3个方法的执行逻辑都是:

(1)task==null,抛出控制针异常

(2)封装方法,这里是不同的地方(Callable、Runnable+null、Runnable+result)

这一步最重要,封装方法就是将 task 和一个 FutureTask 关联起来(主要是FutureTask的状态、当前线程、等待结果队列),这样就可以实现了异步获取结果

(3)execute(ftask)

// 提交一个需要返回结果的任务去执行
public <T> Future<T> submit(Callable<T> task) {
    if (task == null) 
        throw new NullPointerException();

    RunnableFuture<T> ftask = newTaskFor(task);  //封装任务
    execute(ftask);   //执行任务
    return ftask;   //返回结果
}
// 提交一个 不需要 返回结果的任务去执行
public Future<?> submit(Runnable task) {
    if (task == null) 
        throw new NullPointerException();

    RunnableFuture<Void> ftask = newTaskFor(task, null);  //封装任务
    execute(ftask);
    return ftask;
}
// 只有当任务成功被执行后,才会返回给定的结果
public <T> Future<T> submit(Runnable task, T result) {
    if (task == null) 
        throw new NullPointerException();

    RunnableFuture<T> ftask = newTaskFor(task, result);  //封装任务
    execute(ftask);
    return ftask;
}

 

(3)invokeAll 系列

本方法中有很多涉及 FuturnTask 里面的方法,详细见:线程池系列 之 FutureTask 及其底层实现

将集合里面的方法批量执行,不管有没有执行完,都返回 原始的FuturnTask列表(完成还是取消,FuturnTask的状态可以获取),结果可以通过 FuturnTask.get() 获取

invokeAll(Collection<? extends Callable<T>> tasks) 

public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException {

    // 如果任务集为空,则抛出一个NullPointerException
    if (tasks == null)
        throw new NullPointerException();

    // 创建一个和任务数量等大的ArrayList(任务列表!!!)
    ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
    // 标志任务是否完成(注意 invokeAll是所有任务完成时,才能叫完成)
    boolean done = false;
    try {
        // 将tasks里面的每个Callable封装成FuturnTask,添加到futures列表里面,并交给Executor#execute()方法执行
        for (Callable<T> t : tasks) {
            RunnableFuture<T> f = newTaskFor(t);
            futures.add(f);
            execute(f);
        }

        // 判断futures里面的Future是否执行结束,如果还没有完成,通过get()阻塞直到任务完成
        // 也是就说执行完这一段代码,futures里面的每一个任务都是执行完成的情况 
        for (int i = 0, size = futures.size(); i < size; i++) {
            Future<T> f = futures.get(i);   // 获取列表第i个FuturnTask
            // 如果任务没有完成,就通过get阻塞等到它完成;如果已经完成了,就不用管(注意,这里又不是要它的执行结果!!!)
            if (!f.isDone()) {  // 只要任务开始(不是NEW),就是Done
                try {
                    f.get();  //阻塞方法,直到FuturnTask的状态为完成才返回(正常、异常等也算完成)
                } catch (CancellationException ignore) {
                } catch (ExecutionException ignore) {
                }
                // 如果出现异常,自然不会经过 done = true; 
            }
        }
        // 到这里,所有任务都完成了!!!
        done = true;   //标志所有任务都完成了
        return futures;
    } finally {
        // 如果有任务没有完成,就将没有完成的任务取消(注意,可能取消失败)
        if (!done)
            for (int i = 0, size = futures.size(); i < size; i++)
                futures.get(i).cancel(true);
    }
}

invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) 

和上面方法基本一样,就是加了超时判断!!!

public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
                                                                    throws InterruptedException {
    if (tasks == null)
        throw new NullPointerException();

    long nanos = unit.toNanos(timeout);  // 转换时间格式
    ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
    boolean done = false;
    try {
        for (Callable<T> t : tasks)
            futures.add(newTaskFor(t));
    
        // 计算终止时间(!!!)
        final long deadline = System.nanoTime() + nanos;
        final int size = futures.size();

        for (int i = 0; i < size; i++) {
            execute((Runnable)futures.get(i));
            nanos = deadline - System.nanoTime();  //计算剩余时间
            if (nanos <= 0L)  //如果超时,就返回(!!!)
                return futures;
        }

        for (int i = 0; i < size; i++) {
            Future<T> f = futures.get(i);
            if (!f.isDone()) {  // 如果没有完成
                if (nanos <= 0L)  // 超时,则返回剩余任务
                    return futures;
                try {
                    f.get(nanos, TimeUnit.NANOSECONDS);
                } catch (CancellationException ignore) {
                } catch (ExecutionException ignore) {
                } catch (TimeoutException toe) {
                    return futures;
                }
                nanos = deadline - System.nanoTime();  //更新剩余时间
            }
        }
        done = true;
        return futures;
    } finally {
        if (!done)
            for (int i = 0, size = futures.size(); i < size; i++)
                futures.get(i).cancel(true);
    }
}

 

(4)invokeAny 系列

这2个方法就是调用了 doInvokeAny 方法!!!

提交一批任务执行,只要有一个执行完成,就返回他的结果,所以他返回的是最快执行完成的那个任务的结果;当不能获取任何结果时,抛出遇到的最后一个异常

invokeAny(Collection<? extends Callable<T>> tasks) 

public <T> T invokeAny(Collection<? extends Callable<T>> tasks) 
                                        throws InterruptedException, ExecutionException {
    try {
        return doInvokeAny(tasks, false, 0);
    } catch (TimeoutException cannotHappen) {
        assert false;
        return null;
    }
}

 invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) 

public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
                    throws InterruptedException, ExecutionException, TimeoutException {
    return doInvokeAny(tasks, true, unit.toNanos(timeout));
}

 

(5)doInvokeAny

关于 ExecutorCompletionService的介绍见:更好的异步执行:CompletionService 和 ExecutorCompletionService 源码分析

private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks, boolean timed, long nanos)
                            throws InterruptedException, ExecutionException, TimeoutException {

    // 下面代码块就是处理异常
    // 如果任务集合为null,抛出空指针异常
    if (tasks == null)
        throw new NullPointerException();
    int ntasks = tasks.size();
    if (ntasks == 0)  //如果任务数量为0,抛出参数异常
        throw new IllegalArgumentException();

    // 下面代码块是初始化 任务列表和执行器
    // 大小为ntasks大小的 FuturnTask任务列表
    ArrayList<Future<T>> futures = new ArrayList<Future<T>>(ntasks);
    // 一个更好的异步执行器:内部通过 结果阻塞队列 + Executor执行任务 + QueueingFuture将结果放入结果队列
    // 传入this,将this作为作为Executor
    ExecutorCompletionService<T> ecs = new ExecutorCompletionService<T>(this);

    // 为了提高效率,特别是在并行性有限的执行器中,请在提交更多任务之前检查以前提交的任务是否已完成
    // 这种交错加上异常机制解释了主循环的混乱

    try {
        // 用于记录异常,当 不能获得任何结果时 抛出记录的最后一个异常
        ExecutionException ee = null;
        // 根据情况,计算终止时间(如果参数规定不超时,那么就是0)
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        // 任务集合 遍历器
        Iterator<? extends Callable<T>> it = tasks.iterator();

        // 启动一个任务,再渐进式启动其他任务,然后将任务数量减1,活动数量计1(submit就是提交、执行)
        futures.add(ecs.submit(it.next()));
        --ntasks;  
        int active = 1;  //当前正在执行的任务数量(已经提交给ecs的)

        for (;;) {
            Future<T> f = ecs.poll();  // 从ecs的结果队列中取出任务(立刻返回)

            // 如果结果队列为空
            if (f == null) {  

                // 当前没有任务已完成,且还有任务未提交,就继续提交下一个任务
                if (ntasks > 0) {
                    --ntasks;
                    futures.add(ecs.submit(it.next()));
                    ++active;
                }

                // 当前没有任务已完成、所有任务已提交 且没有任务在执行时,退出循环
                else if (active == 0)
                    break;

                // 当前没有任务完成、所有任务已提交 且还有任务在执行时、设置了超时,就超时等待
                else if (timed) {
                    f = ecs.poll(nanos, TimeUnit.NANOSECONDS);
                    if (f == null)
                        throw new TimeoutException();
                    nanos = deadline - System.nanoTime();
                }   

                // 当前没有任务完成、所有任务已提交 且还有任务在执行时、未设置超时,就不限时长地等待
                else
                    f = ecs.take();
            }  //if()

            // 注意,经过上面的if,有可能ecs.take()获取出来了使得f!=null
            // 结果队列非空(有任务已完成):还在执行的任务数自减,返回结果
            if (f != null) {
                --active;
                try {
                    return f.get();
                } catch (ExecutionException eex) {
                    ee = eex;
                } catch (RuntimeException rex) {
                    ee = new ExecutionException(rex);
                }
            }
        }  //for(;;)

        // 说明没有任务正常执行完成返回结果,那么就抛出最后一个异常
        if (ee == null)
            ee = new ExecutionException();
        throw ee;

    } finally {
        // 取消其他任务(卡面poll就将完成任务从队列中删除了)
        for (int i = 0, size = futures.size(); i < size; i++)
            futures.get(i).cancel(true);
    }
}

 

7、ScheduledExecutorService 接口

7.1、简介

Java 定时任务可以用Timer + TimerTask来做,或者使用ScheduledExecutorService,使用ScheduledExecutorService有两个好处:

1. 如果任务执行时间过长,TimerTask会出现延迟执行的情况。比如,第一任务在1000ms执行了4000ms,第二个任务定时在2000ms开始执行。这里由于第一个任务要执行4000,所以第二个任务实际在5000ms开始执行。这是由于Timer是单线程,且顺序执行提交的任务

2. 如果执行任务抛出异常,Timer是不会执行会后面的任务的

使用ScheduledExecutorService可以避免上面两种情况,因为ScheduledExecutorService是线程池,有多个线程执行

ScheduledExecutorService 可以做一些延迟执行的任务,或者周期性执行的任务,它在 JDK 中有一个实现类 ScheduledThreadPoolExecutor,后文再进行分析。

 

7.2、源码分析

public interface ScheduledExecutorService extends ExecutorService {

    // 创建一次性操作(Runnable),该操作会在指定的延迟之后执行(其实就是创建了一个定时任务)
    public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
    // 与上述方法类型,参数类型不同(Callable)
    public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);

    /*
     * 创建并执行一个周期性的任务
     * 执行时间分别为 initialDelay, initialDelay + period, initialDelay + 2*period, 以此类推
     * 若有异常则停止后续的执行;若任务执行时间超过其周期,则后续执行会延迟开始
     */
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit);


    /*
     * 创建并执行一个周期性的任务
     * 该操作在给定的初始延迟之后首先启用,然后在一次执行结束和下一次执行开始之间使用给定的延迟。
     * 该方法与上述有些易混淆,不同的地方在于:
     *      上述方法是以每次开始执行的时间来计算;
     *      而本方法是以每次执行结束的时间和下次开始执行的时间计算
     */
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit);
}

 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值