《线程池系列六》-Guava ListenableFutureTask

Guava中的ListenableFuture提供了添加监听的方法addListener(),该方法添加的监听会在任务执行完成之后执行(看到这就应该想起FutureTake类的done()方法)。就像事件触发回调一样,方便我们进行复杂业务时的逻辑处理。

ListenableFuture接口

该接口继承Future接口,并在Future接口上添加addListener()方法,目的就是添加任务执行完成后的处理逻辑,可以多次调用。不管Future任务为何种状态,都可以调用该方法。
方法定义如下:

void addListener(Runnable listener, Executor executor);

ExecutionList类

在理解ListenableFutureTask之前,我们必须要清楚ExecutionList的作用,因为ListenableFutureTask是将监听器代理给ExecutionList处理的。

  • RunnableExcutorPair类

ExecutionList的内部类,相当于一个结点的定义,维护了任务runnbale信息和执行任务的executor信息,以及下一个监听结点的信息。这是一个典型的链表结构。因为我们可以理解为该类就是链表结点的定义

源码如下:

private static final class RunnableExecutorPair {
    final Runnable runnable;
    final Executor executor;
    @Nullable RunnableExecutorPair next;

    RunnableExecutorPair(Runnable runnable, Executor executor, RunnableExecutorPair next) {
        this.runnable = runnable;
        this.executor = executor;
        this.next = next;
    }
}

从源码的构造方法的最后一句this.next=next,我们可以看出新构建的结点都是插入到最前端,这是典型的链表头插法,说明链表中结点的顺序与提交监听器的任务顺序是相反的。

  • 成员变量

RunnableExcutorPaire:链表结构, 用于维护执行器,每一个节点代表一个执行器
executed:任务(指的是ListenableFuturetask的任务)是否已经执行完,如果任务已经执行完,那么在此之后提交的任务将直接执行(因为任务执行完成后,回调方法已经执行了,不会触发该监听器,所以应该在调用addListener()方法时直接运行)。该状态保证了,addListener()方法可以在任意时刻添加,不受任务执行状态的影响

  • add(Runnable runnable, Executor executor)方法

将runnable和executor构造为RunableExecutorPaire对象,添加到链表中。如果executed为true,那么执行执行刚添加的监听。
源码如下:

public void add(Runnable runnable, Executor executor) {

    Preconditions.checkNotNull(runnable, "Runnable was null.");
    Preconditions.checkNotNull(executor, "Executor was null.");

    synchronized (this) {
        if (!executed) {
            //构建一个节点,添加到runnables链表中,并发环境,所以要加锁
            //头插法
            runnables = new RunnableExecutorPair(runnable, executor, runnables);
            return;
        }
    }

    //已经执行过了,则立即执行
    executeListener(runnable, executor);
}

从源码中可以看出,add()操作会在多线程环境下操作,所以在执行前需要先加锁

  • execute()

执行监听器链表中的任务,首先将链表进行反转,反转的目的就是为了让监听器任务按照添加的顺序执行。(添加是通过头插法插入的,后添加的在链表头),然后执行每一个任务
源码如下:

public void execute() {

    RunnableExecutorPair list;
    synchronized (this) {
        if (executed) {
            return;
        }
        executed = true;
        list = runnables;
        runnables = null;  // allow GC to free listeners even if this stays around for a while.
    }
    //反转链表,保证先添加的任务先执行。
    RunnableExecutorPair reversedList = null;
    while (list != null) {
        RunnableExecutorPair tmp = list;
        list = list.next;
        tmp.next = reversedList;
        reversedList = tmp;
    }

    //依次执行任务
    while (reversedList != null) {
        executeListener(reversedList.runnable, reversedList.executor);
        reversedList = reversedList.next;
    }
}
  • executeListener(Runnable runnable, Executor executor)

使用executor执行runnable方法,仅此而已。
源码如下:

private static void executeListener(Runnable runnable, Executor executor) {
    try {
        //使用执行器执行任务
        executor.execute(runnable);
    } catch (RuntimeException e) {
        // Log it and keep going, bad runnable and/or executor.  Don't
        // punish the other runnables if we're given a bad one.  We only
        // catch RuntimeException because we want Errors to propagate up.
        log.log(Level.SEVERE, "RuntimeException while executing runnable "
                + runnable + " with executor " + executor, e);
    }
}

ListenableFutureTask

其继承FutureTask(主要就是为了重写done()方法),实现ListenableFuture接口
FutureTask的具体工作原理与实现,可以查看《线程池系列一》-FutureTask原理讲解与源码剖析

  • 成员变量

ExecutionList 将添加的监听器任务代理给该对象执行。

  • 构造

构造方法直接调用的FutureTask类的构造方法,不再累述。Guava不建议直接调用构造方法构造对象(构造方法的方法限制为default),而是通过静态的create()方法构造,源码如下:

public static  ListenableFutureTask create(Callable callable) {
    return new ListenableFutureTask(callable);
}

public static  ListenableFutureTask create(
        Runnable runnable, @Nullable V result) {
    return new ListenableFutureTask(runnable, result);
}
  • addListener(Runnable listener, Executor exec)

实现ListenableFuture接口定义的方法,直接代理给executionList,调用executionList的add()方法,源码如下:

@Override
public void addListener(Runnable listener, Executor exec) {
    executionList.add(listener, exec);
}
  • done()

重写FutureTask的done()方法,当任务执行完成之后会调用done()方法,从而执行后续的处理逻辑,在ListenableFutreTask类中,后续的处理逻辑就是添加的监听任务。执行操作直接代理给executionList执行。源码如下:

@Override
protected void done() {
    executionList.execute();
}

总结

该类与ExecutorCompletionService类功能类似,都是通过继承FutureTask,重写done()方法来完成特定的需求。不同之处在于,该类添加了后处理执行任务的操作,而这些操作都是通过代理为ExecutionList执行的,而ExecutorCompletionService只是简单的将执行完成的任务添加到了阻塞队列中。

换句话说:ExecutorCompletionService只是识别哪些任务完成了,ListenableFutureTask是在任务执行完成后添加其他处理逻辑。

欢迎扫描下方二维码,关注公众号,我们可以进行技术交流,共同成长

《线程池系列六》-Guava ListenableFutureTask_第1张图片
qrcode_for_gh_5580beb3cba1_430.jpg

你可能感兴趣的:(《线程池系列六》-Guava ListenableFutureTask)