FutureTask源码解析(1)——预备知识

前言

系列文章目录 java

FutureTask 是一个同步工具类,它实现了Future语义,表示了一种抽象的可生成结果的计算。在包括线程池在内的许多工具类中都会用到,弄懂它的实现将有利于咱们更加深刻地理解Java异步操做实现。编程

在分析它的源码以前, 咱们须要先了解一些预备知识。本篇咱们先来看看FutureTask 中所使用到的接口:RunnableCallableFutureRunnableFuture以及所使用到的工具类ExecutorsUnsafesegmentfault

FutureTask所使用到的接口

Runnable接口

在前面Thread类源码解读的系列文章中咱们说过, 建立线程最重要的是传递一个run()方法, 这个run方法定义了这个线程要作什么事情, 它被抽象成了Runnable接口:设计模式

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

可是, 能够发现, 这个方法并无任何返回值.
若是咱们但愿执行某种类型的操做并拿到它的执行结果, 该怎么办呢?多线程

从 Runnable 到 Callable

要从某种类型的操做中拿到执行结果, 最简单的方式天然是令这个操做本身返回操做结果, 则相较于run方法返回void,咱们能够令一个操做返回特定类型的对象, 这种思路的实现就是Callable接口:并发

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

对比Callable接口与Runnable接口, 咱们能够发现它们最大的不一样点在于:app

  1. Callable有返回值
  2. Callable能够抛出异常

关于有返回值这点,咱们并不意外,由于这就是咱们的需求,call方法的返回值类型采用的泛型,该类型是咱们在建立Callable对象的时候指定的。less

除了有返回值外,相较于Runnable接口,Callable还能够抛出异常,这点看上去好像没啥特别的,可是却有大用处——这意味着若是在任务执行过程当中发生了异常,咱们能够将它向上抛出给任务的调用者来妥善处理,咱们甚至能够利用这个特性来中断一个任务的执行。而Runnable接口的run方法不能抛出异常,只能在方法内部catch住处理,丧失了必定的灵活性。异步

使用Callable接口解决了返回执行结果的问题, 可是也带来了一个新的问题:工具

如何得到执行结果?

有的同窗可能就要说了, 这还不简单? 直接拿不就行了, 看个人:

public static void main(String[] args) {
    Callable<String> myCallable = () -> "This is the results.";
    try {
        String result = myCallable.call();
        System.out.println("Callable 执行的结果是: " + result);
    } catch (Exception e) {
        System.out.println("There is a exception.");
    }
}

这种方法确实能够, 可是它存在几个问题:

  1. call方法是在当前线程中直接调用的, 没法利用多线程。
  2. call方法多是一个特别耗时的操做, 这将致使程序停在myCallable.call()调用处, 没法继续运行, 直到call方法返回。
  3. 若是call方法始终不返回, 咱们没办法中断它的运行。

所以, 理想的操做应当是, 咱们将call方法提交给另一个线程执行, 并在合适的时候, 判断任务是否完成, 而后获取线程的执行结果或者撤销任务, 这种思路的实现就是Future接口:

Future接口

Future接口被设计用来表明一个异步操做的执行结果。你能够用它来获取一个操做的执行结果、取消一个操做、判断一个操做是否已经完成或者是否被取消

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

Future接口一共定义了5个方法:

  • get()

    • 该方法用来获取执行结果, 若是任务还在执行中, 就阻塞等待;
  • get(long timeout, TimeUnit unit)

    • 该方法同get方法相似, 所不一样的是, 它最多等待指定的时间, 若是指定时间内任务没有完成, 则会抛出TimeoutException异常;
  • cancel(boolean mayInterruptIfRunning)

    • 该方法用来尝试取消一个任务的执行, 它的返回值是boolean类型, 表示取消操做是否成功.
  • isCancelled()

    • 该方法用于判断任务是否被取消了。若是一个任务在正常执行完成以前被cancel掉了, 则返回true
  • isDone()

    • 若是一个任务已经结束, 则返回true。注意, 这里的任务结束包含了如下三种状况:

      • 任务正常执行完毕
      • 任务抛出了异常
      • 任务已经被取消

关于cancel方法,这里要补充说几点:
首先有如下三种状况之一的,cancel操做必定是失败的:

  1. 任务已经执行完成了
  2. 任务已经被取消过了
  3. 任务由于某种缘由不能被取消

其它状况下,cancel操做将返回true。值得注意的是,cancel操做返回true并不表明任务真的就是被取消了,这取决于发动cancel状态时任务所处的状态:

  1. 若是发起cancel时任务尚未开始运行,则随后任务就不会被执行;
  2. 若是发起cancel时任务已经在运行了,则这时就须要看mayInterruptIfRunning参数了:

    • 若是mayInterruptIfRunning 为true, 则当前在执行的任务会被中断
    • 若是mayInterruptIfRunning 为false, 则能够容许正在执行的任务继续运行,直到它执行完

这个cancel方法的规范看起来有点绕,如今不太理解不要紧,后面结合实例去看就容易弄明白了,咱们将在下一篇分析FutureTask源码的时候详细说说FutureTask对这一方法的实现。

RunnableFuture 接口

RunnableFuture接口人如其名, 就是同时实现了Runnable接口和Future接口:

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run(); 
}

咱们下一篇开始分析FutureTask的源码的时候就将看到,FutureTask实现了该接口,也就是至关于它同时实现了Runnable接口和Future接口。

有的同窗可能会对这个接口产生疑惑,既然已经继承了Runnable,该接口天然就继承了run方法,为何要在该接口的内部再写一个run方法?

单纯从理论上来讲,这里确实是没有必要的,再多写一遍,我以为大概就是为了看上去直观一点,便于文档或者UML图展现。

FutureTask所使用到的工具类

Executors

Executors 是一个用于建立线程池的工厂类,关于线程池的概念,咱们之后再说。这个类同时也提供了一些有用的静态方法。

前面咱们提到了Callable接口,它是JDK1.5才引入的,而Runnable接口在JDK1.0就有了,咱们有时候须要将一个已经存在Runnable对象转换成Callable对象,Executors工具类为咱们提供了这一实现:

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

能够明显看出来,这个方法采用了设计模式中的适配器模式,将一个Runnable类型对象适配成Callable类型。

由于Runnable接口没有返回值, 因此为了与Callable兼容, 咱们额外传入了一个result参数, 使得返回的Callable对象的call方法直接执行Runnable的run方法, 而后返回传入的result参数。

有的同窗要说了, 你把result参数传进去, 又原封不动的返回出来, 有什么意义呀?
这样作确实没什么意义, result参数的存在只是为了将一个Runnable类型适配成Callable类型.

Unsafe

Unsafe类对于并发编程来讲是个很重要的类,若是你稍微看过J.U.C里的源码(例如咱们前面讲AQS系列的文章里),你会发现处处充斥着这个类的方法调用。

这个类的最大的特色在于,它提供了硬件级别的CAS原子操做。

可能有的同窗会以为这并无什么了不得,CAS的概念都被说烂了。可是,CAS能够说是实现了最轻量级的锁,当多个线程尝试使用CAS同时更新同一个变量时,只有其中的一个线程能成功地更新变量的值,而其余的线程将失败。然而,失败的线程并不会被挂起。

CAS操做包含了三个操做数: 须要读写的内存位置,进行比较的原值,拟写入的新值。

在Unsafe类中,实现CAS操做的方法是: compareAndSwapXXX

例如:

public native boolean compareAndSwapObject(Object obj, long offset, Object expect, Object update);
  • obj是咱们要操做的目标对象
  • offset表示了目标对象中,对应的属性的内存偏移量
  • expect是进行比较的原值
  • update是拟写入的新值。

因此该方法实现了对目标对象obj中的某个成员变量(field)进行CAS操做的功能。

那么,要怎么得到目标field的内存偏移量offset呢? Unsafe类为咱们提供了一个方法:

public native long objectFieldOffset(Field field);

该方法的参数是咱们要进行CAS操做的field对象,要怎么得到这个field对象呢?最直接的办法就是经过反射了:

Class<?> k = FutureTask.class;
Field stateField = k.getDeclaredField("state");

这样一波下来,咱们就能对FutureTask的state属性进行CAS操做了o( ̄▽ ̄)o

除了compareAndSwapObject,Unsafe类还提供了更为具体的对int和long类型的CAS操做:

public native boolean compareAndSwapInt(Object obj, long offset, int expect, int update);
public native boolean compareAndSwapLong(Object obj, long offset, long expect, long update);

从方法签名能够看出,这里只是把目标field的类型限定成int和long类型,而不是通用的Object.

最后,FutureTask还用到了一个方法:

public native void putOrderedInt(Object obj, long offset, int value);

能够看出,该方法只有三个参数,因此它没有比较再交换的概念,某种程度上就是一个赋值操做,即设置obj对象中offset偏移地址对应的int类型的field的值为指定值。这实际上是Unsafe的另外一个方法putIntVolatile的有序或者有延迟的版本,而且不保证值的改变被其余线程当即看到,只有在field被volatile修饰而且指望被意外修改的时候使用才有用。

那么putIntVolatile方法的定义是什么呢?

public native void putIntVolatile(Object obj, long offset, int value);

该方法设置obj对象中offset偏移地址对应的整型field的值为指定值,支持volatile store语义。由此能够看出,当操做的int类型field自己已经被volatile修饰时,putOrderedIntputIntVolatile是等价的。

好了,到这里,基本须要用到的预备知识咱们都学习完了,障碍已经扫清,下一篇咱们就能够愉快地看FutureTask的源码了(๑¯∀¯๑)

(完)

查看更多系列文章: 系列文章目录