系列文章目录 java
FutureTask 是一个同步工具类,它实现了Future语义,表示了一种抽象的可生成结果的计算。在包括线程池在内的许多工具类中都会用到,弄懂它的实现将有利于咱们更加深刻地理解Java异步操做实现。编程
在分析它的源码以前, 咱们须要先了解一些预备知识。本篇咱们先来看看FutureTask 中所使用到的接口:Runnable
、Callable
、Future
、RunnableFuture
以及所使用到的工具类Executors
,Unsafe
。segmentfault
在前面Thread类源码解读的系列文章中咱们说过, 建立线程最重要的是传递一个run()
方法, 这个run方法定义了这个线程要作什么事情, 它被抽象成了Runnable
接口:设计模式
@FunctionalInterface public interface Runnable { public abstract void run(); }
可是, 能够发现, 这个方法并无任何返回值.
若是咱们但愿执行某种类型的操做并拿到它的执行结果, 该怎么办呢?多线程
要从某种类型的操做中拿到执行结果, 最简单的方式天然是令这个操做本身返回操做结果, 则相较于run
方法返回void
,咱们能够令一个操做返回特定类型的对象, 这种思路的实现就是Callable
接口:并发
@FunctionalInterface public interface Callable<V> { V call() throws Exception; }
对比Callable接口与Runnable接口, 咱们能够发现它们最大的不一样点在于:app
关于有返回值这点,咱们并不意外,由于这就是咱们的需求,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."); } }
这种方法确实能够, 可是它存在几个问题:
myCallable.call()
调用处, 没法继续运行, 直到call方法返回。所以, 理想的操做应当是, 咱们将call方法提交给另一个线程执行, 并在合适的时候, 判断任务是否完成, 而后获取线程的执行结果或者撤销任务, 这种思路的实现就是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)
TimeoutException
异常;cancel(boolean mayInterruptIfRunning)
isCancelled()
isDone()
若是一个任务已经结束, 则返回true。注意, 这里的任务结束包含了如下三种状况:
关于cancel方法,这里要补充说几点:
首先有如下三种状况之一的,cancel操做必定是失败的:
其它状况下,cancel操做将返回true。值得注意的是,cancel操做返回true并不表明任务真的就是被取消了,这取决于发动cancel状态时任务所处的状态:
若是发起cancel时任务已经在运行了,则这时就须要看mayInterruptIfRunning
参数了:
mayInterruptIfRunning
为true, 则当前在执行的任务会被中断mayInterruptIfRunning
为false, 则能够容许正在执行的任务继续运行,直到它执行完 这个cancel方法的规范看起来有点绕,如今不太理解不要紧,后面结合实例去看就容易弄明白了,咱们将在下一篇分析FutureTask源码的时候详细说说FutureTask对这一方法的实现。
RunnableFuture接口人如其名, 就是同时实现了Runnable接口和Future接口:
public interface RunnableFuture<V> extends Runnable, Future<V> { void run(); }
咱们下一篇开始分析FutureTask的源码的时候就将看到,FutureTask实现了该接口,也就是至关于它同时实现了Runnable接口和Future接口。
有的同窗可能会对这个接口产生疑惑,既然已经继承了Runnable,该接口天然就继承了run方法,为何要在该接口的内部再写一个run方法?
单纯从理论上来讲,这里确实是没有必要的,再多写一遍,我以为大概就是为了看上去直观一点,便于文档或者UML图展现。
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类对于并发编程来讲是个很重要的类,若是你稍微看过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修饰时,putOrderedInt
和putIntVolatile
是等价的。
好了,到这里,基本须要用到的预备知识咱们都学习完了,障碍已经扫清,下一篇咱们就能够愉快地看FutureTask
的源码了(๑¯∀¯๑)
(完)
查看更多系列文章: 系列文章目录