前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android Handler机制12之Callable、Future和FutureTask

Android Handler机制12之Callable、Future和FutureTask

作者头像
隔壁老李头
发布2018-08-30 16:08:08
6600
发布2018-08-30 16:08:08
举报
文章被收录于专栏:Android 研究Android 研究
本片文章的主要内容如下:
  • 1、概述
  • 2、Callable
  • 3、Future
  • 4、FutureTask
  • 5、总结

一、概述

  • Java从发布的一个版本开始就可以很方便地编写多线程的应用程序,并在设计中引入异步处理。创建线程有两种方式,一种是直接继承Thread,另外一种就是实现Runnable接口。Thread类、Runnable接口和Java内存管理模型使得多线程编程简单直接。但是大家知道Thread和Runnable接口都不允许声明检查性异常,也不能返定义返回值。没有返回值这点稍微有点麻烦。
  • 不能声明抛出检查型异常则更麻烦一些。public void run()方法契约意味着你必须捕获并处理检查型异常。即使你小心地保存了异常信息(在捕获异常时)以便稍后检查, 但也不能保证这个类(Runnnable)的所有使用者都读取这个异常信息。你也可以修改Runnable实现的getter,让他们都能抛出任务执行中的异常。但这种方法除了繁琐也不是十分安全可靠,你不能强迫使用者调用这些方法,程序员很可能会调用join()方法等待线程结束后就不管了。
  • 但是现在不用担心了,以上问题终于在1.5中解决了。Callable接口和Future接口接口的引入以及他们对线程池的支持优雅地解决了这个两个问题。

二、Callable

Callable.java源码地址

(一) Runnable

说到Callable就不能不说下java.lang.Runnable,它是一个接口,它只声明了一个run()方法,由于这个run()方法的返回值是void的,所以在执行完任务之后无法返回任何结果。

public  interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}
(二) Callable

Callable位于java.util.concurrent包下,它也是一个接口,在它里面也只声明了一个方法,只不过这个方法叫做call()

/**
 * A task that returns a result and may throw an exception.
 * Implementors define a single method with no arguments called
 * {@code call}.
 *
 * <p>The {@code Callable} interface is similar to {@link
 * java.lang.Runnable}, in that both are designed for classes whose
 * instances are potentially executed by another thread.  A
 * {@code Runnable}, however, does not return a result and cannot
 * throw a checked exception.
 *
 * <p>The {@link Executors} class contains utility methods to
 * convert from other common forms to {@code Callable} classes.
 *
 * @see Executor
 * @since 1.5
 * @author Doug Lea
 * @param <V> the result type of method {@code call}
 */
@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
     //
    V call() throws Exception;
}

可以看到,这一个泛型的接口,call()函数返回的类型就是传递进来的V类型。

(三) Callable的类注释

为了让我们更好的理解Callable,还是从类的注释开始,翻译如下:

  • 有执行结果的,并且可以引发异常的任务 它的实现类需要去实现没有参数的的方法,即call()方法
  • Callable有点类似于Runnable接口,因为它们都是设计成被线程去执行的可执行代码的实例。由于Runnable是没有返回值的,并且不能抛出一个检查出的异常。
  • Executors类包含方法可以使得Callable转化成其他普通形式。
(四)、 Runnable和Callable的区别:

  • 1、Runnable是Java 1.1有的,而Callable是1.5之后才加上去的
  • 2、Callable规定的方法是call(),Runnable规定的方法是run()
  • 3、Callable的任务执行后可返回值,而Runnable的任务是不能返回(因为是void)
  • 4、call()方法是可以抛出异常的,而run()方法不可以
  • 5、运行Callable任务可以拿到一个Future对象,表示异步计算的结果,它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果,通过Future对象可以了解任务的执行情况,可取消任务的执行,还可获取执行结果
  • 6、加入线程池运行,Runnable使用ExecutorService的execute()方法,Callable使用submit()方法

三、Future

Future.java源码地址

(一) Future类详解

Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞知道任务返回结果。Future类位于java.util.concurrent包下,它是一个接口:

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

在Future接口中声明了5个方法,下面依次解释下每个方法的作用:

  • boolean cancel(boolean mayInterruptIfRunning):方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置为true,则表示可以取消正在执行过程中任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
  • boolean isCancelled():表示任务是否被取消成功,如果在任务正常完成之前取消成功则返回true.
  • isDone():方法表示任务是否已经完成,若任务完成,则返回true。
  • V get():方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回
  • V get(long timeout, TimeUnit unit):用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。

总结一下,Future提供了三种功能:

  • 判断任务是否完成
  • 能够中断任务
  • 能够获取任务的执行结果
(二) Future类注释

  • Future可以表示异步计算的结果。Future提供一个方法用来检查这个计算是否已经完成,还提供一个方法用来检索计算结果。get()方法可以获取计算结果,这个方法里面可能产生阻塞,如果产生了阻塞了,就阻塞到计算结束。cancel()方法可以取消执行。还有一些方法可以用来确定任务是否已经完成、是否已经取消成功了。如果任务已经执行完毕,则是不能取消的。如果你想使用Future并且,希望它是不可撤销的,同时不关心执行的结果,可以声明Future的泛型,并且基础任务返回值结果为null。
  • 使用示例

class App { ExecutorService executor = ... ArchiveSearcher searcher = ... void showSearch(final String target) throws InterruptedException { Future<String> future = executor.submit(new Callable<String>() { public String call() { return searcher.search(target); } }); displayOtherThings(); // do other things while searching try { displayText(future.get()); // use future } catch (ExecutionException ex) { cleanup(); return; } } } FutureTask是Future的具体实现类,同时也实现了Runnable。所以FutureTask可以被Executor执行。例如Executor的submit方法可以换成如下写法 FutureTask<String> future = new FutureTask<>(new Callable<String>() { public String call() { return searcher.search(target); }}); executor.execute(future); 内存一致性效应:如果想在另一个线程调用 Future.get()方法,则在调用该方法之前应该先执行其自己的操作。

(三) boolean cancel(boolean mayInterruptIfRunning)方法注释

翻译如下:

尝试去关闭正在执行的任务,如果任务已经完成,或者任务已经被取消,或者任务因为某种原因而无法被取消,则关闭事失败。当这个任务还没有被执行,则调用此方法会成功,并且这个任务将来不会被执行。如果任务已经开始了,mayInterruptIfRunning这个入参决定是否应该中断该任务。这个方法被执行返回后,再去调用isDone()方法,将一直返回true。如果调用这个方法后返回true,再去调用isCancelled()方法,则isCancelled()方法一直返回true。mayInterruptIfRunning这个参数表示的是该任务的线程是否可以被中断,true表示可以中断,如果这个任务不能被取消,则返回false,而大多数这种情况是任务已完成。

上面已经提到了Future只是一个接口,所以是无法直接用来创建对象使用的,在注释里面推荐使用FutureTask,那我们就来看下FutureTask

四、FutureTask

FutureTask.java源码地址 我们先来看一下FutureTask的实现:

(一)、RunnableFuture

通过代码我们知道

public class FutureTask<V> implements RunnableFuture<V>

说明FutureTask类实现了RunnableFuture接口,我们看一下RunnableFuture接口

RunnableFuture.java源码地址

/**
 * A {@link Future} that is {@link Runnable}. Successful execution of
 * the {@code run} method causes completion of the {@code Future}
 * and allows access to its results.
 * @see FutureTask
 * @see Executor
 * @since 1.6
 * @author Doug Lea
 * @param <V> The result type returned by this Future's {@code get} method
 */
public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

通过代码我知道RunnableFuture继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行,也可以作为Future得到Callable的返回值

(二)、FutureTask的类注释
/**
 * A cancellable asynchronous computation.  This class provides a base
 * implementation of {@link Future}, with methods to start and cancel
 * a computation, query to see if the computation is complete, and
 * retrieve the result of the computation.  The result can only be
 * retrieved when the computation has completed; the {@code get}
 * methods will block if the computation has not yet completed.  Once
 * the computation has completed, the computation cannot be restarted
 * or cancelled (unless the computation is invoked using
 * {@link #runAndReset}).
 *
 * <p>A {@code FutureTask} can be used to wrap a {@link Callable} or
 * {@link Runnable} object.  Because {@code FutureTask} implements
 * {@code Runnable}, a {@code FutureTask} can be submitted to an
 * {@link Executor} for execution.
 *
 * <p>In addition to serving as a standalone class, this class provides
 * {@code protected} functionality that may be useful when creating
 * customized task classes.
 *
 * @since 1.5
 * @author Doug Lea
 * @param <V> The result type returned by this FutureTask's {@code get} methods
 */

为了更好的理解作者设计,先来看下类注释,翻译如下:

  • 一个可以取消的异步执行,这个类是Future的基础实现类,提供一下方法,比如可以去开启和关闭执行,查询是否已经执行完毕,以及检索计算的结果。这个执行结果只能等执行完毕才能获取,如果还未执行完毕则处于阻塞状态。除非调用runAndReset()方法,否则一旦计算完毕后则无法重启启动或者取消。
  • Callable或者Runnable可以包装FutureTask,由于FutureTask实现Runnable,所以在Executor可以执行FutureTask
  • 除了可以作为一个独立的类外,FutureTask还提供一些protected方法,这样在自定义任务类是,就会很方便。
(三)、FutureTask的结构

结构如下图

FutureTask结构图.png

继承关系如下:

继承关系.png

(四)、静态final类WaitNode
    /**
     * Simple linked list nodes to record waiting threads in a Treiber
     * stack.  See other classes such as Phaser and SynchronousQueue
     * for more detailed explanation.
     */
    static final class WaitNode {
        volatile Thread thread;
        volatile WaitNode next;
        WaitNode() { thread = Thread.currentThread(); }
    }

先翻译一下注释

精简版的链表结构,其中每一个节点代表堆栈中等待的线程。如果想了解更多的详细说明,请参考其他类比如Phaser和SynchronousQueue

结构如下图

WaitNode.png

再来看下构造函数和成员变量

  • thread:代表等待的线程
  • next:代表下一个节点,通过这个节点我们也能退出这个链表是单向链表
  • 构造函数:无参的构造函数里面将thread设置为当前线程。 总结: WaitNode就是一个链表结构,用于记录等待当前FutureTask结果的线程。
(五)、FutureTask的状态

FutureTask一共有7种状态,代码如下:

    /*
     * Revision notes: This differs from previous versions of this
     * class that relied on AbstractQueuedSynchronizer, mainly to
     * avoid surprising users about retaining interrupt status during
     * cancellation races. Sync control in the current design relies
     * on a "state" field updated via CAS to track completion, along
     * with a simple Treiber stack to hold waiting threads.
     *
     * Style note: As usual, we bypass overhead of using
     * AtomicXFieldUpdaters and instead directly use Unsafe intrinsics.
     */

    /**
     * The run state of this task, initially NEW.  The run state
     * transitions to a terminal state only in methods set,
     * setException, and cancel.  During completion, state may take on
     * transient values of COMPLETING (while outcome is being set) or
     * INTERRUPTING (only while interrupting the runner to satisfy a
     * cancel(true)). Transitions from these intermediate to final
     * states use cheaper ordered/lazy writes because values are unique
     * and cannot be further modified.
     *
     * Possible state transitions:
     * NEW -> COMPLETING -> NORMAL
     * NEW -> COMPLETING -> EXCEPTIONAL
     * NEW -> CANCELLED
     * NEW -> INTERRUPTING -> INTERRUPTED
     */
    private volatile int state;
    private static final int NEW          = 0;
    private static final int COMPLETING   = 1;
    private static final int NORMAL       = 2;
    private static final int EXCEPTIONAL  = 3;
    private static final int CANCELLED    = 4;
    private static final int INTERRUPTING = 5;
    private static final int INTERRUPTED  = 6;

老规矩先来翻译一下注释,我们看到注释有两部分,我们依次翻译如下:

  • 上半部分注释: 修订说明:和之前版本的AbstractQueuedSynchronizer不同,主要是为了避免令人惊讶的用户在取消竞争迁建保留中断的状态。在当前设计中,同步的控制是通过CAS中的更新字段——"state"来完成跟踪的。并且通过一个Treiber堆栈来保存这些等待的线程。风格笔记(笔者注:这个真心不知道怎么翻译,谁知道请在下面留言):和往常一样,我们直接使用不安全的内在函数, 并且忽略使用AtomicXFieldUpdaters的开销。

简单的说就是,FutureTask中使用state表示任务状态,state变更由CAS操作保证原子性。

  • 下半部分注释: 这个任务的运行状态,最初是NEW状态,在调用set()方法或者setException()方法或者cancel()方法后,运行的状态就变为终端的状态。在运行期间,如果计算出结果后,状态变更为COMPLETING,如果通过调用cancel(true)安全的中断运行,则状态变更为INTERRUPTING。由于值是唯一且不能被进一步修改,所以从中间状态到最终状态的转化是有序的。
  • 可能的状态变更流程
  • NEW -> COMPLETING -> NORMAL
  • NEW -> COMPLETING -> EXCEPTIONAL
  • NEW -> CANCELLED
  • NEW -> INTERRUPTING -> INTERRUPTED

上面翻译我感觉不是很好,用白话解释一下:

FutureTask对象初始化时,在构造器把state设置为NEW,之后状态变更依据具体执行情况来定。

  • 任务执行正常,并且还没结束,state为COMPLETING,代表任务正在执行即将完成,接下来很快会被设置为NORMAL或者EXCEPTIONAL,这取决于调用Runnable中的call()方法是否抛出异常,没有异常则是NORMAL,抛出异常是EXCEPTIONAL。
  • 任务提交后、任务结束前取消任务,都有可能变为CANCELLED或者INTERRUPTED。在调用cancel(boolean) 是,如果传入false表示不中断线程,state会变成CANCELLED,如果传入true,则state先变为 INTERRUPTING,中断完成后,变为INTERRUPTED。

总结一下就是:FutureTask的状态变化过程为,以下4种情况:

  • 任务正常执行并返回: NEW -> COMPLETING -> NORMAL
  • 任务执行中出现异常:NEW -> COMPLETING -> EXCEPTIONAL
  • 任务执行过程中被取消,并且不中断线程:NEW -> CANCELLED
  • 任务执行过程中被取消,并且中断线程:NEW -> INTERRUPTING -> INTERRUPTED

那我们就简单的解释下几种状态

  • NEW:任务初始化状态
  • COMPLETING:任务正在完成状态(任务已经执行完成,但是结果还没有赋值给outcome)
  • NORMAL:任务完成(结果已经赋值给outcome)
  • EXCEPTIONAL:任务执行异常
  • CANCELLED:任务被取消
  • INTERRUPTING:任务被中断中
  • INTERRUPTED:任务被中断
(六)、FutureTask的成员变量
    /** The underlying callable; nulled out after running */
    private Callable<V> callable;
    /** The result to return or exception to throw from get() */
    private Object outcome; // non-volatile, protected by state reads/writes
    /** The thread running the callable; CASed during run() */
    private volatile Thread runner;
    /** Treiber stack of waiting threads */
    private volatile WaitNode waiters;

  • callable:任务具体执行体,具体要做的事
  • outcome:任务的执行结果,get()方法的返回值
  • runner:任务的执行线程
  • waiters:获取任务结果的等待线程(是一个链式列表)
(七)、FutureTask的构造函数

FutureTask有两个构造函数 分别是FutureTask(Callable<V> callable)和FutureTask(Runnable runnable, V result),那我们来依次分析

1、构造函数 FutureTask(Callable<V> callable)
    /**
     * Creates a {@code FutureTask} that will, upon running, execute the
     * given {@code Callable}.
     *
     * @param  callable the callable task
     * @throws NullPointerException if the callable is null
     */
    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

老规矩先翻译一下注释:

创建一个FutureTask,并在将来执行的时候,运行传入的Callable。

看下代码,我们知道:

  • 1、通过代码我们知道如果传入的Callable为空直接抛出异常,说明构造时传入的Callable不能为空。
  • 2、设置当前状态为NEW。

所以总结一句话就是,通过传入Callable来构造一个FutureTask。

2、构造函数 FutureTask(Runnable runnable, V result)
    /**
     * Creates a {@code FutureTask} that will, upon running, execute the
     * given {@code Runnable}, and arrange that {@code get} will return the
     * given result on successful completion.
     *
     * @param runnable the runnable task
     * @param result the result to return on successful completion. If
     * you don't need a particular result, consider using
     * constructions of the form:
     * {@code Future<?> f = new FutureTask<Void>(runnable, null)}
     * @throws NullPointerException if the runnable is null
     */
    public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
    }

老规矩先翻译一下注释:

创建一个FutureTask,并在将来执行的时候,运行传入的Runnable,并且将成功完成后的结果返给传入的result。

看下代码,我们知道:

  • 1、先通过调用Executors的callable(Runnable, T)方法返回的Callable
  • 2、将上面返回的Callable指向本地变量callable
  • 3、设置当前状态为NEW。

所以总结一句话就是,通过传入Runnable来构造一个任务

这里顺带说下Executors.callable(runnable, result)方法的内部实现

2.1、Executors.callable(Runnable, T)
    /**
     * Returns a {@link Callable} object that, when
     * called, runs the given task and returns the given result.  This
     * can be useful when applying methods requiring a
     * {@code Callable} to an otherwise resultless action.
     * @param task the task to run
     * @param result the result to return
     * @param <T> the type of the result
     * @return a callable object
     * @throws NullPointerException if task null
     */
    public static <T> Callable<T> callable(Runnable task, T result) {
        if (task == null)
            throw new NullPointerException();
        return new RunnableAdapter<T>(task, result);
    }

方法内部很简单,就是new了一个RunnableAdapter并返回,那我们来看下RunnableAdapterr适配器

    /**
     * A callable that runs given task and returns given result.
     */
    private static final class RunnableAdapter<T> implements Callable<T> {
        private final Runnable task;
        private final T result;
        RunnableAdapter(Runnable task, T result) {
            this.task = task;
            this.result = result;
        }
        public T call() {
            task.run();
            return result;
        }
    }

通过上面代码我们知道:

  • RunnableAdapter是FutureTask的一个静态内部类并且实现了Callable,也就是说RunnableAdapter是Callable子类。
  • call方法实现代码是,执行Runnable的run方法,并返回构造函数传入的result参数。

这里实际上是将一个Runnable对象伪装成一个Callable对象,是适配器对象。

3、构造函数总结

通过分析上面两个构造函数,我们知道无论采用第一个构造函数,还是第二个构造函数,其结果都是给本地变量callable初始化赋值,所以说FutureTask最终都是执行Callable类型的任务。然后设置状态为NEW。

(八)、FutureTask的几个核心方法

FutureTask有几个核心方法:

  • public void run():表示任务的执行
  • public V get()和public V get(long timeout, TimeUnit unit):表示获取任务的结果
  • public boolean cancel(boolean mayInterruptIfRunning):表示取消任务

那我们就依次来看下

1、run()方法
    /**
     * 判断任务的状态是否是初始化的状态
     * 判断执行任务的线程对象runner是否为null,为空就将当前执行线程赋值给runner属性
     *  不为空说明应有线程准备执行这个任务了
     */
    public void run() {
        if (state != NEW ||
            !U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
            return;
        try {
           // 任务状态时NEW,并且callable不为空,则执行任务
           // 如果认为被cancel了,callable会被置空
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                
                V result;  // 结果的变量
                
                boolean ran;  // 执行完毕的变量
                try {
                    // 执行任务并返回结果
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    // 执行异常
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    //任务执行完毕就设置结果
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            // 将执行任务的执行线程清空 
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                //判断线程的状态
                handlePossibleCancellationInterrupt(s);
        }
    }

通过上面代码和注释我们知道run方法内部的流程如下:

  • 第一步:检查当前任务是否是NEW以及runner是否为空,这一步是防止任务被取消
  • 第二步:double-check任务状态和state
  • 第三步:执行业务逻辑,也就是c.call()方法被执行
  • 第四步:如果业务逻辑异常,则调用setException方法将异常对象赋值给outcome,并更新state的值
  • 第五步:如果业务正常,则调用set方法将执行结果赋给outcome,并更新state值。
1.1、Unsafe类

Java不能够直接访问操作系统底层,而是通过本地方法来访问。Unsafe提供了硬件级别的原子访问,主要提供以下功能:

  • 分配释放内存
  • 定位某个字段的内存位置
  • 挂起一个线程和恢复,更多的是通过LockSupport来访问。park和unpark
  • CAS操作,比较一个对象的某个位置的内存值是否与期望值一致。

主要方法是compareAndSwap()

1.1.1UNSAFE.compareAndSwapObject(this,runnerOffset,null, Thread.currentThread())方法

UNSAFE.compareAndSwapObject(this,RUNNER,null, Thread.currentThread())这行代码什么意思?

compareAndSwapObject可以通过反射,根据偏移量去修改对象,第一个参数表示要修改的对象,第二个表示偏移量,第三个参数用于和偏移量对应的值进行比较,第四个参数表示如何偏移量对应的值和第三个参数一样时要把偏移量设置成的值。

翻译成白话文就是:

如果this对象的RUNNER偏移地址的值是null,那就把它设置为Thread.currentThread()。

上面提到了一个概念是RUNNER,那这个RUNNER 是什么东东? 我们在源码中找到

    // Unsafe mechanics
    private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
    private static final long STATE;
    private static final long RUNNER;
    private static final long WAITERS;
    static {
        try {
            STATE = U.objectFieldOffset
                (FutureTask.class.getDeclaredField("state"));
            RUNNER = U.objectFieldOffset
                (FutureTask.class.getDeclaredField("runner"));
            WAITERS = U.objectFieldOffset
                (FutureTask.class.getDeclaredField("waiters"));
        } catch (ReflectiveOperationException e) {
            throw new Error(e);
        }

        // Reduce the risk of rare disastrous classloading in first call to
        // LockSupport.park: https://bugs.openjdk.java.net/browse/JDK-8074773
        Class<?> ensureLoaded = LockSupport.class;
    }

它对应的就是runner的成员变量,也就是说如果状态不是NEW或者runner不是null,run方法直接返回。

所以我们知道

  • private static final long STATE:表示state这个成员变量
  • private static final long RUNNER:表示的是runner这个成员变量
  • rivate static final long WAITERS:表示的是waiters这个成员变量

在这个run方法里面分别调用了setException(Throwable )和set(V)方法,那我们就来详细看下

1.2、setException(Throwable t)方法
    /**
     * Causes this future to report an {@link ExecutionException}
     * with the given throwable as its cause, unless this future has
     * already been set or has been cancelled.
     *
     * <p>This method is invoked internally by the {@link #run} method
     * upon failure of the computation.
     *
     * @param t the cause of failure
     */
    protected void setException(Throwable t) {
         // state状态 NEW-> COMPLETING
        if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {
            outcome = t;
             // // COMPLETING -> EXCEPTIONAL 到达稳定状态
            U.putOrderedInt(this, STATE, EXCEPTIONAL); // final state
            // 一些 结束工作
            finishCompletion();
        }
    }

简单翻译一下方法的注释:

  • 除非这个Future已经设置过了,或者被取消了,否则这个产生的异常将会汇报到ExecutionException里面
  • 如果在run()方法里面产生了异常,则会调用这个方法

所以总结一下就是:当任务执行过程中出现异常时候,对异常的处理方式

PS:这个方法是protected,所以可以重写

1.3、set(V v)方法

这个方法主要是:执行结果的赋值操作

    /**
     * Sets the result of this future to the given value unless
     * this future has already been set or has been cancelled.
     *
     * <p>This method is invoked internally by the {@link #run} method
     * upon successful completion of the computation.
     *
     * @param v the value
     */
    protected void set(V v) {
        // state 状态  NEW->COMPLETING
        if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {
            outcome = v;
            // COMPLETING -> NORMAL 到达稳定状态
            U.putOrderedInt(this, STATE, NORMAL); // final state
            // 一些结束工作
            finishCompletion();
        }
    }

通过上面我们知道这个方法内部的流程如下:

  • 首先 将任务的状态改变
  • 其次 将结果赋值
  • 再次 改变任务状态
  • 最后 处理等待线程队列(将线程阻塞状态改为唤醒,这样等待线程就拿到结果了)

PS:这里使用的是 UNSAFE的putOrderedInt方法,其实就是原子量的LazySet内部使用的方法,为什么要用这个方法?首先LazySet相对于Volatile-Write来说更加廉价,因为它没有昂贵的Store/Load屏障,其次后续线程不会及时的看到state从COMPLETING变为NORMAL,但这没有什么关系,而且NORMAL是state最终的状态,不会再变化了。

在这个方法里面调用了finishCompletion()方法,那我们就来看下这个方法

1.4、finishCompletion()方法

这个方法是:

在任务执行完成(包括取消、正常结束、发生异常),将等待线程队列唤醒,同时让任务执行体清空。

代码如下:

    /**
     * Removes and signals all waiting threads, invokes done(), and
     * nulls out callable.
     */
    private void finishCompletion() {
        // assert state > COMPLETING;
        // 遍历等待节点
        for (WaitNode q; (q = waiters) != null;) {
            if (U.compareAndSwapObject(this, WAITERS, q, null)) {
                for (;;) {
                    Thread t = q.thread;
                    if (t != null) {
                        q.thread = null;
                        // 唤醒等待线程
                        LockSupport.unpark(t);
                    }
                    WaitNode next = q.next;
                    if (next == null)
                        break;
                    q.next = null; // unlink to help gc
                    q = next;
                }
                break;
            }
        }
        // 这里可以自定义实现任务完成后要做的事情(在子类重写done()方法)
        done();
        // 清空callable
        callable = null;        // to reduce footprint
    }

由代码和注释可以看出来,这里就是遍历WaitNode链表,对每一个WaitNode对应的线程依次进行LockSupport.unpark(t),使其结束阻塞。WaitNode通知完毕后,调用done方法。目前该方法是空实现,所以如果你想在任务完成后执行一些业务逻辑可以重写这个方法。所以这个方法主要是在于唤醒等待线程。由前面知道,当任务正常结束或者异常结束时,都会调用finishCompletion()去唤醒等待线程。这时候等待线程就可以醒来,可以获取结果了。

·

1.4.1、LockSupport简介

这里首先说下LockSupport,很多新手对这个东西,不是很熟悉,我先简单说下,这里就不详细说明了。

LockSupport是构建concurrent包的基础之一

####### ① 操作对象 LockSupport调用Unsafe的natvie代码:

public native void unpark(Thread jthread); 
public native void park(boolean isAbsolute, long time); 

这两个函数声明清楚地说明了操作对象:park函数是将当前Thread阻塞,而unPark函数则是将另一个Thread唤醒。

与Object类的wait/notify 机制相比,park/unpark有两个优点:

  • 1、以thread为操作对象更符合阻塞线程的直观定义
  • 2、操作更精准,可以准确地唤醒某一个线程(notify随机唤醒一个线程,notifyAll唤醒所有等待的线程),增加了灵活性

####### ② 关于许可 在上面的文件,使用了阻塞和唤醒,是为了和wait/notify做对比。其实park/unpark的设计原理核心是"许可"。park是等待一个许可。unpark是为某线程提供一个"许可"。如果说某线程A调用park,那么除非另外一个线程unpark(A)给A一个许可,否则线程A将阻塞在park操作上。

1.5、handlePossibleCancellationInterrupt(int) 方法
    /**
     * Ensures that any interrupt from a possible cancel(true) is only
     * delivered to a task while in run or runAndReset.
     */
    private void handlePossibleCancellationInterrupt(int s) {
        // It is possible for our interrupter to stall before getting a
        // chance to interrupt us.  Let's spin-wait patiently.
        // 如果当前正在中断过程中,自等待,等中断完成
        if (s == INTERRUPTING)
            while (state == INTERRUPTING)
                Thread.yield(); // wait out pending interrupt

        // assert state == INTERRUPTED;

        // We want to clear any interrupt we may have received from
        // cancel(true).  However, it is permissible to use interrupts
        // as an independent mechanism for a task to communicate with
        // its caller, and there is no way to clear only the
        // cancellation interrupt.
        //
        // Thread.interrupted();
    }

先来看下注释:

执行计算而不设置其结果,然后重新设置future为初始化状态,如果执行遇到异常或者任务被取消,则不再执行此操作。这样设计的目的是:执行多次任务。

看代码我们知道他主要就是: 如果其他线程正在终止该任务,那么运行该任务的线程就暂时让出CPU时间一直到state= INTERRUPTED为止。

所以它的作用就是:

确保cancel(true) 产生的中断发生在run()或者 runAndReset()方法过程中

2、get()与get(long, TimeUnit)方法

任务是由线程池提供的线程执行,那么这时候主线程则会阻塞,直到任务线程唤醒它们。我们看看get()是怎么做的?

2.1 get()方法

代码如下:

    /**
     * @throws CancellationException {@inheritDoc}
     */
    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        // state 小于 COMPLETING 则说明任务仍然在执行,且没有被取消
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }

通过代码我们知道

  • 首先 校验参数
  • 然后 判断是否正常执行且没有被取消,如果没有则调用awaitDone(boolean,long)方法
  • 最后 调用report(int) 方法

这里面涉及两个方法分别是awaitDone(boolean,long)方法和report(int) 方法,那让我们依次来看下。

2.1.1 awaitDone(boolean,long)方法

这个方法主要是等待任务执行完毕,如果任务取消或者超时则停止 代码如下:

    /**
     * Awaits completion or aborts on interrupt or timeout.
     *
     * @param timed true if use timed waits
     * @param nanos time to wait, if timed
     * @return state upon completion or at timeout
     */
    private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
        // The code below is very delicate, to achieve these goals:
        // - call nanoTime exactly once for each call to park
        // - if nanos <= 0L, return promptly without allocation or nanoTime
        // - if nanos == Long.MIN_VALUE, don't underflow
        // - if nanos == Long.MAX_VALUE, and nanoTime is non-monotonic
        //   and we suffer a spurious wakeup, we will do no worse than
        //   to park-spin for a while
        // 起始时间
        long startTime = 0L;    // Special value 0L means not yet parked
        // 当前等待线程的节点
        WaitNode q = null;
         // 是否将节点放在了等待列表中
        boolean queued = false;
        // 通过死循环来实现线程阻塞等待
        for (;;) {
            int s = state;
            if (s > COMPLETING) {
                 // 任务可能已经完成或者被取消了
                if (q != null)
                    q.thread = null;
                return s;
            }
            else if (s == COMPLETING)
                // We may have already promised (via isDone) that we are done
                // so never return empty-handed or throw InterruptedException
                // 任务线程可能被阻塞了,让出cpu
                Thread.yield();
            else if (Thread.interrupted()) {
                // 线程中断则移除等待线程并抛出异常
                removeWaiter(q);
                throw new InterruptedException();
            }
            else if (q == null) {
                // 等待节点为空,则初始化新节点并关联当前线程
                if (timed && nanos <= 0L)
                   // 如果需要等待,并且等待时间小于0表示立即,则直接返回
                    return s;
                // 如果不需要等待,或者需要等待但是等待时间大于0。
                q = new WaitNode();
            }
            else if (!queued)
                // 等待线程入队,因为如果入队成功则queued=true
                queued = U.compareAndSwapObject(this, WAITERS,
                                                q.next = waiters, q);
            else if (timed) {
                //如果有超时设置
                final long parkNanos;
                if (startTime == 0L) { // first time
                    startTime = System.nanoTime();
                    if (startTime == 0L)
                        startTime = 1L;
                    parkNanos = nanos;
                } else {
                    long elapsed = System.nanoTime() - startTime;
                    if (elapsed >= nanos) {
                        // 已经超时,则移除等待节点
                        removeWaiter(q);
                        return state;
                    }
                    parkNanos = nanos - elapsed;
                }
                // nanoTime may be slow; recheck before parking
                if (state < COMPLETING)
                    // 任务还在执行,且没有被取消,所以继续等待
                    LockSupport.parkNanos(this, parkNanos);
            }
            else
                LockSupport.park(this);
        }
    }

两个入参:

  • timed 为true 表示设置超时时间,false表示不设置超时间
  • nanos 表示超时的状态

for死循环里面的逻辑如下:

  • 第一步 判断任务是否已经完处于完成或者取消了,如果直接返回转状态值,如果不是,则走第二步
  • 第二步,如果状态值是COMPLETING,则说明当前是在set()方法时被阻塞了,所以只需要让出当前线程的CPU资源。
  • 第三步,如果线程已经中断了,则移除线程并抛出异常
  • 第四步,如果能走到这一步,状态值只剩下NEW了,如果状态值是NEW,并且q==null,则说明这是第一次,所以初始化一个当前线程的等待节点。
  • 第五步,此时queued=false,说明如果还没入队,则它是在等待入队
  • 第六步,能走到这一步,说明queued=true,这时候判断timed是否为true,如果为true则设置了超时时间,然后看一下startTime是否为0,如果为0,则说明是第一次,因为startTime默认值为0,如果是第一此,则设置startTime=1。保证startTime==0是第一次。如果startTime!=0,则说明不是第一次,如果不是第一次,则需要计算时差elapsed,如果elapsed大于nanos,则说明超时,如果小于则没有超时,还有时差,然等待这个时差阻塞。
  • 第七步,如果timed为false,则说明没有超时设置。

所以总结一下:

waitDone就是将当前线程加入等待队列(waitNode有当前Thread的Thread变量),然后用LockSupport将自己阻塞,等待超时或者被解除阻塞后,判断是否已经完成(state为>= COMPLETING),如果未完成(state< COMPLETING)抛出超时异常,如果已完成则稍等或者直接返回结果。

这个方法里面调用了removeWaiter(WaitNode) 这个方法,所以我们来先看这个这个removeWaiter(WaitNode) 里面是怎么实现的

2.1.2 removeWaiter(WaitNode)
    /**
     * Tries to unlink a timed-out or interrupted wait node to avoid
     * accumulating garbage.  Internal nodes are simply unspliced
     * without CAS since it is harmless if they are traversed anyway
     * by releasers.  To avoid effects of unsplicing from already
     * removed nodes, the list is retraversed in case of an apparent
     * race.  This is slow when there are a lot of nodes, but we don't
     * expect lists to be long enough to outweigh higher-overhead
     * schemes.
     */
    private void removeWaiter(WaitNode node) {
        if (node != null) {
            // 将node 的thread 域置空
            node.thread = null;
            /**
             * 下面过程中会将node从等待队列中移除,以thread为null为依据
             * 如果过程中发生了竞争,重试
             */
            retry:
            for (;;) {          // restart on removeWaiter race
                for (WaitNode pred = null, q = waiters, s; q != null; q = s) {
                    s = q.next;
                    if (q.thread != null)
                        pred = q;
                    else if (pred != null) {
                        pred.next = s;
                        if (pred.thread == null) // check for race
                            continue retry;
                    }
                    else if (!U.compareAndSwapObject(this, WAITERS, q, s))
                        continue retry;
                }
                break;
            }
        }
    }

首先来看下类的注释

为了防止累积的内存垃圾,所以需要去取消超时或者已经被中断的等待节点。内部节点因为没有CAS所以很简单,所以他们可以被无害的释放。为了避免已删除节点的影响,如果存在竞争的情况下,需要重新排列。所以当节点很多是,速度会很慢,因此我们不建议列表太长而导致效率降低。

这个方法主要就是将线程节点从等待队列中移除

2.1.2 report(int) 方法
    /**
     * Returns result or throws exception for completed task.
     *
     * @param s completed state value
     */
    @SuppressWarnings("unchecked")
    private V report(int s) throws ExecutionException {
        Object x = outcome;
         // 如果任务正常执行完成,返回任务执行结果
        if (s == NORMAL)
            return (V)x;

        // 如果任务被取消,抛出异常
        if (s >= CANCELLED)
            throw new CancellationException();
        
        // 其他状态 抛出执行异常 ExecutionException 
        throw new ExecutionException((Throwable)x);
    }

report.png

如果任务处于NEW、COMPLETING和INTERRUPTING 这三种状态的时候是执行不到report方法的,所以没有对这三种状态尽心转换。

2.2 get(long, TimeUnit)方法

最多等待为计算完成所给的时间之后,获取其结果

    /**
     * @throws CancellationException {@inheritDoc}
     */
    public V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
        if (unit == null)
            throw new NullPointerException();
        int s = state;
        if (s <= COMPLETING &&
            (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
            throw new TimeoutException();
        return report(s);
    }

有参get方法源码很简洁,首先校验参数,然后根据state状态判断是否超时,如果超时则异常,不超时则调用report去获取最终结果。 当 s <= COMPLETING 时,表明任务仍然在执行且没有被取消,如果它为true,那么走到awaitDone方法。关于awaitDone方法上面已经讲解了,这里就不过阐述了。

3、cancel(boolean)方法

只能取消还没有被执行的任务(任务状态为NEW的任务)

    public boolean cancel(boolean mayInterruptIfRunning) {
        // 如果任务状态不是初始化状态,则取消任务
         //如果此时任务已经执行了,并且可能执行完成,但是状态改变还没有来得及修改,也就是在run()方法中的set()方法还没来得及调用
         //   继续判断任务的当前状态时否为NEW,因为此时执行任务线程可能再度获得处理了,任务状态可能已发生改变
        if (!(state == NEW &&
              U.compareAndSwapInt(this, STATE, NEW,
                  mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
                return false;

        // 如果任务状态依然是NEW,也就是执行线程没有改变任务的状态,
        // 则让执行线程中断(在这个过程中执行线程可能会改变任务的状态)
        try {    // in case call to interrupt throws exception
            if (mayInterruptIfRunning) {
                try {
                    Thread t = runner;
                    if (t != null)
                        t.interrupt();
                } finally { // final state
                    // 将任务状态设置为中断
                    U.putOrderedInt(this, STATE, INTERRUPTED);
                }
            }
        } finally {
            // 处理任务完成的结果
            finishCompletion();
        }
        return true;
    }

通过上述代码,我们知道这个取消不一定起作用的。

上面的代码逻辑如下:

  • 第一步:state不等于NEW,则表示任务即将进入最终状态 ,则state == NEW为false,导致if成立直接返回false
  • 第二步:如果mayInterruptIfRunning为true在,表示中断线程,则设置状态为INTERRUPTING,中断之后设置为INTERRUPTED。如果mayInterruptIfRunning为false,表示不中断线程,把state设置为CANCELLED
  • 第三步:state状态为NEW,任务可能已经开始执行,也可能还未开始,所以用Unsafe查看下,如果不是,则直接返回false
  • 第四步:移除等待线程
  • 第五步:唤醒

所以,cancel()方法改变了futureTask的状态为,如果传入的是false,并且业务逻辑已经开始执行,当前任务是不会被终止的,而是会继续执行,知道异常或者执行完毕。如果传入的是true,会调用当前线程的interrupt()方法,把中断标志位设为true。

事实上,除非线程自己停止自己的任务,或者退出JVM,是没有其他方法完全终止一个线程任务的。mayInterruptIfRunning=true,通过希望当前线程可以响应中断的方式来结束任务。当任务被取消后,会被封装为CancellationException抛出。

4、runAndReset() 方法

任务可以被多次执行

    /**
     * Executes the computation without setting its result, and then
     * resets this future to initial state, failing to do so if the
     * computation encounters an exception or is cancelled.  This is
     * designed for use with tasks that intrinsically execute more
     * than once.
     *
     * @return {@code true} if successfully run and reset
     */
    protected boolean runAndReset() {
        if (state != NEW ||
            !U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
            return false;
        boolean ran = false;
        int s = state;
        try {
            Callable<V> c = callable;
            if (c != null && s == NEW) {
                try {
                    c.call(); // don't set result
                    ran = true;
                } catch (Throwable ex) {
                    setException(ex);
                }
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
        return ran && s == NEW;
    }

先来看下注释:

执行计算而不设置其结果,然后重新设置future为初始化状态,如果执行遇到异常或者任务被取消,则不再执行此操作。这样设计的目的是:执行多次任务。

我们可以对比一下runAndReset与run方法,其实两者相差不大,主要就是有两点区别

  • 1 run()方法里面设置了result的值,而runAndReset()则移除了这段代码

下面我们就来看下handlePossibleCancellationInterrupt(int) 这个方法

五、总结

FutureTask大部分就简单分析完了,其他的自己看下就行了。FutureTask中的任务状态由变量state表示,任务状态都是基于state判断。而FutureTask的阻塞则是通过自旋+挂起线程实现的。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017.09.21 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、概述
  • 二、Callable
    • (一) Runnable
      • (二) Callable
        • (三) Callable的类注释
          • (四)、 Runnable和Callable的区别:
          • 三、Future
            • (一) Future类详解
              • (二) Future类注释
                • (三) boolean cancel(boolean mayInterruptIfRunning)方法注释
                • 四、FutureTask
                  • (一)、RunnableFuture
                    • (二)、FutureTask的类注释
                      • (三)、FutureTask的结构
                        • (四)、静态final类WaitNode
                          • (五)、FutureTask的状态
                            • (六)、FutureTask的成员变量
                              • (七)、FutureTask的构造函数
                                • (八)、FutureTask的几个核心方法
                                • 五、总结
                                领券
                                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档