Java中「Future」接口详解
Posted 知了一笑
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java中「Future」接口详解相关的知识,希望对你有一定的参考价值。
主打一手结果导向;
一、背景
在系统中,异步执行任务,是很常见的功能逻辑,但是在不同的场景中,又存在很多细节差异;
有的任务只强调「执行过程」,并不需要追溯任务自身的「执行结果」,这里并不是指对系统和业务产生的效果,比如定时任务、消息队列等场景;
但是有些任务即强调「执行过程」,又需要追溯任务自身的「执行结果」,在流程中依赖某个异步结果,判断流程是否中断,比如「并行」处理;
【串行处理】整个流程按照逻辑逐步推进,如果出现异常会导致流程中断;
【并行处理】主流程按照逻辑逐步推进,其他「异步」交互的流程执行完毕后,将结果返回到主流程,如果「异步」流程异常,会影响部分结果;
此前在《「订单」业务》的内容中,聊过关于「串行」和「并行」的应用对比,即在订单详情的加载过程中,通过「并行」的方式读取:商品、商户、订单、用户等信息,提升接口的响应时间;
二、Future接口
1、入门案例
异步是对流程的解耦,但是有的流程中又依赖异步执行的最终结果,此时就可以使用「Future」接口来达到该目的,先来看一个简单的入门案例;
public class ServerTask implements Callable<Integer>
@Override
public Integer call() throws Exception
Thread.sleep(2000);
return 3;
public class FutureBase01
public static void main(String[] args) throws Exception
TimeInterval timer = DateUtil.timer();
// 线程池
ExecutorService executor = Executors.newFixedThreadPool(3);
// 批量任务
List<ServerTask> serverTasks = new ArrayList<>() ;
for (int i=0;i<3;i++)
serverTasks.add(new ServerTask());
List<Future<Integer>> taskResList = executor.invokeAll(serverTasks) ;
// 结果输出
for (Future<Integer> intFuture:taskResList)
System.out.println(intFuture.get());
// 耗时统计
System.out.println("timer...interval = "+timer.interval());
这里模拟一个场景,以线程池批量执行异步任务,在任务内线程休眠2秒,以并行的方式最终获取全部结果,只耗时2秒多一点,如果串行的话耗时肯定超过6秒;
2、Future接口
Future表示异步计算的结果,提供了用于检查计算是否完成、等待计算完成、以及检索计算结果的方法。
【核心方法】
get()
:等待任务完成,获取执行结果,如果任务取消会抛出异常;get(long timeout, TimeUnit unit)
:指定等待任务完成的时间,等待超时会抛出异常;isDone()
:判断任务是否完成;isCancelled()
:判断任务是否被取消;cancel(boolean mayInterruptIfRunning)
:尝试取消此任务的执行,如果任务已经完成、已经取消或由于其他原因无法取消,则此尝试将失败;
【基础用法】
public class FutureBase02
public static void main(String[] args) throws Exception
// 线程池执行任务
ExecutorService executor = Executors.newFixedThreadPool(3);
FutureTask<String> futureTask = new FutureTask<>(new Callable<String>()
@Override
public String call() throws Exception
Thread.sleep(3000);
return "task...OK";
) ;
executor.execute(futureTask);
// 任务信息获取
System.out.println("是否完成:"+futureTask.isDone());
System.out.println("是否取消:"+futureTask.isCancelled());
System.out.println("获取结果:"+futureTask.get());
System.out.println("尝试取消:"+futureTask.cancel(Boolean.TRUE));
【FutureTask】
Future接口的基本实现类,提供了计算的启动和取消、查询计算是否完成以及检索计算结果的方法;
在「FutureTask」类中,可以看到线程异步执行任务时,其中的核心状态转换,以及最终结果写出的方式;
虽然「Future」从设计上,实现了异步计算的结果获取,但是通过上面的案例也可以发现,流程的主线程在执行get()
方法时会阻塞,直到最终获取结果,显然对于程序来说并不友好;
在JDK1.8
提供「CompletableFuture」类,对「Future」进行优化和扩展;
三、CompletableFuture类
1、基础说明
「CompletableFuture」类提供函数编程的能力,可以通过回调的方式处理计算结果,并且支持组合操作,提供很多方法来实现异步编排,降低异步编程的复杂度;
「CompletableFuture」实现「Future」和「CompletionStage」两个接口;
- Future:表示异步计算的结果;
- CompletionStage:表示异步计算的一个步骤,当一个阶段计算完成时,可能会触发其他阶段,即步骤可能由其他CompletionStage触发;
【入门案例】
public class CompletableBase01
public static void main(String[] args) throws Exception
// 线程池
ExecutorService executor = Executors.newFixedThreadPool(3);
// 任务执行
CompletableFuture<String> cft = CompletableFuture.supplyAsync(() ->
try
Thread.sleep(3000);
catch (InterruptedException e)
e.printStackTrace();
return "Res...OK";
, executor);
// 结果输出
System.out.println(cft.get());
2、核心方法
2.1 实例方法
public class Completable01
public static void main(String[] args) throws Exception
// 线程池
ExecutorService executor = Executors.newFixedThreadPool(3);
// 1、创建未完成的CompletableFuture,通过complete()方法完成
CompletableFuture<Integer> cft01 = new CompletableFuture<>() ;
cft01.complete(99) ;
// 2、创建已经完成CompletableFuture,并且给定结果
CompletableFuture<String> cft02 = CompletableFuture.completedFuture("given...value");
// 3、有返回值,默认ForkJoinPool线程池
CompletableFuture<String> cft03 = CompletableFuture.supplyAsync(() -> return "OK-3";);
// 4、有返回值,采用Executor自定义线程池
CompletableFuture<String> cft04 = CompletableFuture.supplyAsync(() -> return "OK-4";,executor);
// 5、无返回值,默认ForkJoinPool线程池
CompletableFuture<Void> cft05 = CompletableFuture.runAsync(() -> );
// 6、无返回值,采用Executor自定义线程池
CompletableFuture<Void> cft06 = CompletableFuture.runAsync(()-> , executor);
2.2 计算方法
public class Completable02
public static void main(String[] args) throws Exception
// 线程池
ExecutorService executor = Executors.newFixedThreadPool(3);
CompletableFuture<String> cft01 = CompletableFuture.supplyAsync(() ->
try
Thread.sleep(2000);
catch (InterruptedException e)
e.printStackTrace();
return "OK";
,executor);
// 1、计算完成后,执行后续处理
// cft01.whenComplete((res, ex) -> System.out.println("Result:"+res+";Exe:"+ex));
// 2、触发计算,如果没有完成,则get设定的值,如果已完成,则get任务返回值
// boolean completeFlag = cft01.complete("given...value");
// if (completeFlag)
// System.out.println(cft01.get());
// else
// System.out.println(cft01.get());
//
// 3、开启新CompletionStage,重新获取线程执行任务
cft01.whenCompleteAsync((res, ex) -> System.out.println("Result:"+res+";Exe:"+ex),executor);
2.3 结果获取方法
public class Completable03
public static void main(String[] args) throws Exception
// 线程池
ExecutorService executor = Executors.newFixedThreadPool(3);
CompletableFuture<String> cft01 = CompletableFuture.supplyAsync(() ->
try
Thread.sleep(2000);
catch (InterruptedException e)
e.printStackTrace();
return "Res...OK";
,executor);
// 1、阻塞直到获取结果
// System.out.println(cft01.get());
// 2、设定超时的阻塞获取结果
// System.out.println(cft01.get(4, TimeUnit.SECONDS));
// 3、非阻塞获取结果,如果任务已经完成,则返回结果,如果任务未完成,返回给定的值
// System.out.println(cft01.getNow("given...value"));
// 4、get获取抛检查异常,join获取非检查异常
System.out.println(cft01.join());
2.4 任务编排方法
public class Completable04
public static void main(String[] args) throws Exception
// 线程池
ExecutorService executor = Executors.newFixedThreadPool(3);
CompletableFuture<String> cft01 = CompletableFuture.supplyAsync(() ->
try
Thread.sleep(2000);
catch (InterruptedException e)
e.printStackTrace();
System.out.println("OK-1");
return "OK";
,executor);
// 1、cft01任务执行完成后,执行之后的任务,此处不关注cft01的结果
// cft01.thenRun(() -> System.out.println("task...run")) ;
// 2、cft01任务执行完成后,执行之后的任务,可以获取cft01的结果
// cft01.thenAccept((res) ->
// System.out.println("cft01:"+res);
// System.out.println("task...run");
// );
// 3、cft01任务执行完成后,执行之后的任务,获取cft01的结果,并且具有返回值
// CompletableFuture<Integer> cft02 = cft01.thenApply((res) ->
// System.out.println("cft01:"+res);
// return 99 ;
// );
// System.out.println(cft02.get());
// 4、顺序执行cft01、cft02
// CompletableFuture<String> cft02 = cft01.thenCompose((res) -> CompletableFuture.supplyAsync(() ->
// System.out.println("cft01:"+res);
// return "OK-2";
// ));
// cft02.whenComplete((res,ex) -> System.out.println("Result:"+res+";Exe:"+ex));
// 5、对比任务的执行效率,由于cft02先完成,所以取cft02的结果
// CompletableFuture<String> cft02 = cft01.applyToEither(CompletableFuture.supplyAsync(() ->
// System.out.println("run...cft02");
// try
// Thread.sleep(3000);
// catch (InterruptedException e)
// e.printStackTrace();
//
// return "OK-2";
// ),(res) ->
// System.out.println("either...result:" + res);
// return res;
// );
// System.out.println("finally...result:" + cft02.get());
// 6、两组任务执行完成后,对结果进行合并
// CompletableFuture<String> cft02 = CompletableFuture.supplyAsync(() -> "OK-2") ;
// String finallyRes = cft01.thenCombine(cft02,(res1,res2) ->
// System.out.println("res1:"+res1+";res2:"+res2);
// return res1+";"+res2 ;
// ).get();
// System.out.println(finallyRes);
CompletableFuture<String> cft02 = CompletableFuture.supplyAsync(() ->
System.out.println("OK-2");
return "OK-2";
) ;
CompletableFuture<String> cft03 = CompletableFuture.supplyAsync(() ->
System.out.println("OK-3");
return "OK-3";
) ;
// 7、等待批量任务执行完返回
// CompletableFuture.allOf(cft01,cft02,cft03).get();
// 8、任意一个任务执行完即返回
System.out.println("Sign:"+CompletableFuture.anyOf(cft01,cft02,cft03).get());
2.5 异常处理方法
public class Completable05
public static void main(String[] args) throws Exception
// 线程池
ExecutorService executor = Executors.newFixedThreadPool(3);
CompletableFuture<String> cft01 = CompletableFuture.supplyAsync(() ->
if (1 > 0)
throw new RuntimeException("task...exception");
return "OK";
,executor);
// 1、捕获cft01的异常信息,并提供返回值
String finallyRes = cft01.thenApply((res) ->
System.out.println("cft01-res:" + res);
return res;
).exceptionally((ex) ->
System.out.println("cft01-exe:" + ex.getMessage());
return "error" ;
).get();
System.out.println("finallyRes="+finallyRes);
CompletableFuture<String> cft02 = CompletableFuture.supplyAsync(() ->
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
return "OK-2";
,executor);
// 2、如果cft02未完成,则get时抛出指定异常信息
boolean exeFlag = cft02.completeExceptionally(new RuntimeException("given...exception"));
if (exeFlag)
System.out.println(cft02.get());
else
System.out.println(cft02.get());
3、线程池问题
- 在实践中,通常不使用
ForkJoinPool#commonPool()
公共线程池,会出现线程竞争问题,从而形成系统瓶颈; - 在任务编排中,如果出现依赖情况或者父子任务,尽量使用多个线程池,从而避免任务请求同一个线程池,规避死锁情况发生;
四、CompletableFuture原理
1、核心结构
在分析「CompletableFuture」其原理之前,首先看一下涉及的核心结构;
【CompletableFuture】
在该类中有两个关键的字段:「result」存储当前CF的结果,「stack」代表栈顶元素,即当前CF计算完成后会触发的依赖动作;从上面案例中可知,依赖动作可以没有或者有多个;
【Completion】
依赖动作的封装类;
【UniCompletion】
继承Completion类,一元依赖的基础类,「executor」指线程池,「dep」指依赖的计算,「src」指源动作;
【BiCompletion】
继承UniCompletion类,二元或者多元依赖的基础类,「snd」指第二个源动作;
2、零依赖
顾名思义,即各个CF之间不产生依赖关系;
public class DepZero
public static void main(String[] args) throws Exception
ExecutorService executor = Executors.newFixedThreadPool(3);
CompletableFuture<String> cft1 = CompletableFuture.supplyAsync(()-> "OK-1",executor);
CompletableFuture<String> cft2 = CompletableFuture.supplyAsync(()-> "OK-2",executor);
System.out.println(cft1.get()+";"+cft2.get());
3、一元依赖
即CF之间的单个依赖关系;这里使用「thenApply」方法演示,为了看到效果,使「cft1」长时间休眠,断点查看「stack」结构;
public class DepOne
public static void main(String[] args) throws Exception
ExecutorService executor = Executors.newFixedThreadPool(3);
CompletableFuture<String> cft1 = CompletableFuture.supplyAsync(() ->
try
Thread.sleep(30000);
catch (InterruptedException e)
e.printStackTrace();
return "OK-1";
,executor);
CompletableFuture<String> cft2 = cft1.thenApply(res ->
System.out.println("cft01-res"+res);
return "OK-2" ;
);
System.out.println("cft02-res"+cft2.get());
断点截图:
原理分析:
观察者Completion注册到「cft1」,注册时会检查计算是否完成,未完成则观察者入栈,当「cft1」计算完成会弹栈;已完成则直接触发观察者;
可以调整断点代码,让「cft1」先处于完成状态,再查看其运行时结构,从而分析完整的逻辑;
4、二元依赖
即一个CF同时依赖两个CF;这里使用「thenCombine」方法演示;为了看到效果,使「cft1、cft2」长时间休眠,断点查看「stack」结构;
public class DepTwo
public static void main(String[] args) throws Exception
ExecutorService executor = Executors.newFixedThreadPool(3);
CompletableFuture<String> cft1 = CompletableFuture.supplyAsync(() ->
try
Thread.sleep(30000);
catch (InterruptedException e)
e.printStackTrace();
return "OK-1";
,executor);
CompletableFuture<String> cft2 = CompletableFuture.supplyAsync(() ->
try
Thread.sleep(30000);
catch (InterruptedException e)
e.printStackTrace();
return "OK-2";
,executor);
// cft3 依赖 cft1和cft2 的计算结果
CompletableFuture<String> cft3 = cft1.thenCombine(cft2,(res1,res2) ->
System.out.println("cft01-res:"+res1);
System.out.println("cft02-res:"+res2);
return "OK-3" ;
);
System.out.println("cft03-res:"+cft3.get());
断点截图:
原理分析:
在「cft1」和「cft2」未完成的状态下,尝试将BiApply压入「cft1」和「cft2」两个栈中,任意CF完成时,会尝试触发观察者,观察者检查「cft1」和「cft2」是否都完成,如果完成则执行;
5、多元依赖
即一个CF同时依赖多个CF;这里使用「allOf」方法演示;为了看到效果,使「cft1、cft2、cft3」长时间休眠,断点查看「stack」结构;
public class DepMore
public static void main(String[] args) throws Exception
ExecutorService executor = Executors.newFixedThreadPool(3);
CompletableFuture<String> cft1 = CompletableFuture.supplyAsync(() ->
try
Thread.sleep(30000);
catch (InterruptedException e)
e.printStackTrace();
return "OK-1";
,executor);
CompletableFuture<String> cft2 = CompletableFuture.supplyAsync(() ->
try
Thread.sleep(30000);
catch (InterruptedException e)
e.printStackTrace();
return "OK-2";
,executor);
CompletableFuture<String> cft3 = CompletableFuture.supplyAsync(() ->
try
Thread.sleep(30000);
catch (InterruptedException e)
e.printStackTrace();
return "OK-3";
,executor);
// cft4 依赖 cft1和cft2和cft3 的计算结果
CompletableFuture<Void> cft4 = CompletableFuture.allOf(cft1,cft2,cft3);
CompletableFuture<String> finallyRes = cft4.thenApply(tm ->
System.out.println("cft01-res:"+cft1.join());
System.out.println("cft02-res:"+cft2.join());
System.out.println("cft03-res:"+cft3.join());
return "OK-4";
);
System.out.println("finally-res:"+finallyRes.get());
断点截图:
原理分析:
多元依赖的回调方法除了「allOf」还有「anyOf」,其实现原理都是将依赖的多个CF补全为平衡二叉树,从断点图可知会按照树的层级处理,核心结构参考二元依赖即可;
五、参考源码
编程文档:
https://gitee.com/cicadasmile/butte-java-note
应用仓库:
https://gitee.com/cicadasmile/butte-flyer-parent
java.util.concuttent Callable Future详解
在传统的多线程实现方式中(继承Thread和实现Runnable)无法直接获取线程执行的返回结果,如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦。
从Java 1.5开始,java.util.concurrent包中提供了 Callable和 Future两个接口,通过它们就可以在任务执行完毕之后得到任务执行结果。
Callable
Callable与Runnable的功能大致相似,Callable中有一个call()函数,但是call()函数有返回值,而Runnable的run()函数不能将结果返回给客户程序。
Future
Executor就是Runnable和Callable的调度容器,Future就是对于具体的Runnable或者Callable任务的执行结果进行
取消、查询是否完成、获取结果、设置结果操作。get方法会阻塞,直到任务返回结果。
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; }
- cancel方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
- isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
- isDone方法表示任务是否已经完成,若任务完成,则返回true;
- get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
- get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。
也就是说Future提供了三种功能:
- 判断任务是否完成
- 能够中断任务
- 能够获取任务的执行结果
Future和FutureTask区别
FutureTask是为了弥补Thread的不足而设计的,它可以让程序员准确地知道线程什么时候执行完成并获得到线程执行完成后返回的结果(如果有需要)。
FutureTask是一种可以取消的异步的计算任务。它的计算是通过Callable实现的,它等价于可以携带结果的Runnable,并且有三个状态:等待、运行和完成。完成包括所有计算以任意的方式结束,包括正常结束、取消和异常。
Executor框架利用FutureTask来完成异步任务,并可以用来进行任何潜在的耗时的计算。一般FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
Example1:
package cn.com.example.concurrent.future; import java.util.concurrent.*; /** * Created by Jack on 2017/1/24. */ public class RunnableFutureTask { /** * ExecutorService */ static ExecutorService mExecutor = Executors.newSingleThreadExecutor(); /** * @param args */ public static void main(String[] args) { runnableDemo(); futureDemo(); } /** * runnable, 无返回值 */ static void runnableDemo() { new Thread(new Runnable() { @Override public void run() { System.out.println("runnable demo : " + fibc(20)); } }).start(); } /** * 其中Runnable实现的是void run()方法,无返回值;Callable实现的是 V * call()方法,并且可以返回执行结果。其中Runnable可以提交给Thread来包装下 * ,直接启动一个线程来执行,而Callable则一般都是提交给ExecuteService来执行。 */ static void futureDemo() { try { /** * 提交runnable则没有返回值, future没有数据 */ Future<?> result = mExecutor.submit(new Runnable() { @Override public void run() { fibc(20); } }); System.out.println("future result from runnable : " + result.get()); /** * 提交Callable, 有返回值, future中能够获取返回值 */ Future<Integer> result2 = mExecutor.submit(new Callable<Integer>() { @Override public Integer call() throws Exception { return fibc(20); } }); System.out.println("future result from callable : " + result2.get()); /** * FutureTask则是一个RunnableFuture<V>,即实现了Runnbale又实现了Futrue<V>这两个接口, * 另外它还可以包装Runnable(实际上会转换为Callable)和Callable * <V>,所以一般来讲是一个符合体了,它可以通过Thread包装来直接执行,也可以提交给ExecuteService来执行 * ,并且还可以通过v get()返回执行结果,在线程体没有执行完成的时候,主线程一直阻塞等待,执行完则直接返回结果。 */ FutureTask<Integer> futureTask = new FutureTask<Integer>( new Callable<Integer>() { @Override public Integer call() throws Exception { return fibc(20); } }); // 提交futureTask mExecutor.submit(futureTask); System.out.println("future result from futureTask : " + futureTask.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } /** * 效率底下的斐波那契数列, 耗时的操作 * * @param num * @return */ static int fibc(int num) { if (num == 0) { return 0; } if (num == 1) { return 1; } return fibc(num - 1) + fibc(num - 2); } }
结果:
runnable demo : 6765 future result from runnable : null future result from callable : 6765 future result from futureTask : 6765
Example2:
package cn.com.example.concurrent.future; import java.util.Random; import java.util.concurrent.*; /** * Created by Jack on 2017/1/24. */ public class RunnableFutureTask { public static void main(String[] args) { // 初始化一个Callable对象和FutureTask对象 Callable pAccount = new PrivateAccount(); FutureTask futureTask = new FutureTask(pAccount); // 使用futureTask创建一个线程 Thread pAccountThread = new Thread(futureTask); System.out.println("futureTask线程现在开始启动,启动时间为:" + System.nanoTime()); pAccountThread.start(); System.out.println("主线程开始执行其他任务"); // 从其他账户获取总金额 int totalMoney = new Random().nextInt(100000); System.out.println("现在你在其他账户中的总金额为" + totalMoney); System.out.println("等待私有账户总金额统计完毕..."); // 测试后台的计算线程是否完成,如果未完成则等待 while (!futureTask.isDone()) { try { Thread.sleep(500); System.out.println("私有账户计算未完成继续等待..."); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("futureTask线程计算完毕,此时时间为" + System.nanoTime()); Integer privateAccountMoney = null; try { privateAccountMoney = (Integer) futureTask.get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println("您现在的总金额为:" + totalMoney + privateAccountMoney.intValue()); } } class PrivateAccount implements Callable { Integer totalMoney; @Override public Object call() throws Exception { Thread.sleep(5000); totalMoney = new Integer(new Random().nextInt(10000)); System.out.println("您当前有" + totalMoney + "在您的私有账户中"); return totalMoney; } }
结果:
futureTask线程现在开始启动,启动时间为:88171383410225 主线程开始执行其他任务 现在你在其他账户中的总金额为2838 等待私有账户总金额统计完毕... 私有账户计算未完成继续等待... 私有账户计算未完成继续等待... 私有账户计算未完成继续等待... 私有账户计算未完成继续等待... 私有账户计算未完成继续等待... 私有账户计算未完成继续等待... 私有账户计算未完成继续等待... 私有账户计算未完成继续等待... 私有账户计算未完成继续等待... 您当前有9238在您的私有账户中 私有账户计算未完成继续等待... futureTask线程计算完毕,此时时间为88176389195218 您现在的总金额为:28389238
以上是关于Java中「Future」接口详解的主要内容,如果未能解决你的问题,请参考以下文章
CallableFutureTask和Future详解带你理解java并发编程
java.util.concuttent Callable Future详解
Future接口和Callable接口以及FeatureTask详解