在前文中我们可以得知使用ThresdPoolExecutor的execute方法,是不能够获取线程的执行结果的,那么如何获取返回值呢?future 和FutureTask是java用以完成这一要求的功能。
1 . Future
首先我们可以看到Future是 java.util.concurrent包下面的接口,他包括了这么几个方法:
//取消任务的方法
boolean cancel(boolean mayInterruptIfRunning);
//判断任务是否已经取消的方法
boolean isCancelled();
//判断任务是否已经结束的方法
boolean isDone();
//获取任务执行结果的方法
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
因为Future是一个接口,这里我们可以看到重点的实现类有这么几个:FutureTask 以及AbstractFuture
AbstractFuture的实现与FutureTask的实现非常相似,FutureTask使用unsafe包的CAS实现,而AbstractFuture使用的是AQS(AbstractQueuedSynchronizer,jdk锁实现的模板类),AQS以及AbstractFuture会在更下面来讨论,这里先讲解FutureTask的实现
1.1 FutureTask
FutureTask<V> implements RunnableFuture
FutureTask直接实现了一个叫做RunnableFuture的接口
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这两个接口的一个包装,实际上就是在成功执行run()方法后,可以通过Future访问执行结果。这样FutureTask就同时可以包装 runnable和callable对象以及被传递到Excetor去执行。简单写个例子:
public class FutureTest {
//线程池
private final static ThreadPoolExecutor executorService = new ThreadPoolExecutor(3, 3, 1L, TimeUnit.MINUTES,
new ArrayBlockingQueue<>(1), new ThreadPoolExecutor.DiscardPolicy());
public static void main(String[] args) throws ExecutionException, InterruptedException {
Future <String>future1 = executorService.submit(() -> {
System.out.println("start four " + Thread.currentThread().getName());
return "one ";
});
Future future2 = executorService.submit(() -> System.out.println("start two " + Thread.currentThread().getName()));
Future future3 = executorService.submit(() -> System.out.println("start three " + Thread.currentThread().getName()));
Future<String> future4 = executorService.submit(() -> {
System.out.println("start four " + Thread.currentThread().getName());
return "four ";
});
System.out.println("task one res:" + future1.get() + Thread.currentThread().getName());
System.out.println("task two res:" + future2.get() + Thread.currentThread().getName());
System.out.println("task three res:" + future3.get() + Thread.currentThread().getName());
System.out.println("task four res:" + future4.get() + Thread.currentThread().getName());
executorService.shutdown();//关闭线程池,阻塞直到所有任务执行完毕
}
}
执行结果如下
FutureTask的核心方法如下:
FutureTask的两个构造方法:
/**
*FutureTask 实现Callable
*/
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
/**
* 实现Callable 并返回结果
*/
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
public boolean isCancelled() {
return state >= CANCELLED;
}
public boolean isDone() {
return state != NEW;
}
简单讲一个核心的方法:
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
首先判断 state状态,如果状态小于COMPLETING也就是处于NEW状态,就调用awaitDone方法
* 可能的执行顺序
* 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;
awaitDone方法 中存在一个无限循环方法 判断state和等待队列的状态queued,还有等待节点WaitNode
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
for (;;) {
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
int s = state;
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
else if (q == null)
q = new WaitNode();
else if (!queued)
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
LockSupport.parkNanos(this, nanos);
}
else
LockSupport.park(this);
}
}
这个等待节点的操作和判断是用UNSAFE.compareAndSwapObject(cas方法)来进行判断的,cas方法前文有叙述过,这里就不再赘述了。如果时间超过deadline就直接返回状态结果,如果没有则调用LockSupport方法创建locks的基本线程阻塞基元,如果有时间单位就给这个阻塞加上时间,没有就直接产生阻塞
/**
简单的用一个栈存储等待中的线程节点
*/
static final class WaitNode {
volatile Thread thread;
volatile WaitNode next;
WaitNode() { thread = Thread.currentThread(); }
}
通过状态判断以后执行report方法:
@SuppressWarnings("unchecked")
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL)
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
返回具体线程的执行状态。
Future 的优缺点
优点
- 1、可配合线程池使用,也可以单独创建,适合使用在经典的线程池中。 一些缺点:
- 1、没有提供通知的机制,导致无法得知future何时完成
- 2、如果使用阻塞的future.get。等待future返回的结果其实是同步操作,使用isDone()轮询判断的话,会耗费cpu的资源
1.2 ComplteableFuture
java 8为了解决FutureTask的一些性能问题, 引入了ComplteableFuture
,解决了FutureTask中许多的问题,并且吸收了所有Google Guava中ListenableFuture和SettableFuture的特征,还提供了其它强大的功能,让Java拥有了完整的非阻塞编程模型。
CompletableFuture
实现了 Future 和CompletionStage两个接口,
class CompletableFuture<T> implements Future<T>, CompletionStage<T
Future 的接口上文已经叙述了,这里不再概述,首先来看CompletionStage接口:
CompletionStage的方法命名
CompletionStage接口有三十多个方法,其实这么多的方法都是实现了组合逻辑:
正常处理的异步计算的逻辑分为三种:
- 前一个计算完成以后再调度第二个计算, 也就是then的逻辑
- 两个计算都完成以后再进行下一个计算, 也就是both的逻辑
- 两个计算其中一个完成以后就进行下一个计算, 也就是either的逻辑
- 方法中如果含有apply字样就意味这个组合的方式是function,即接受前一个计算的结果,并且计算以后返回一个新的结果。
- 方法中如果含有accept字样就意味这组合的方式是consumer,即接受前一个计算的结果,并且计算以后不返回有意义的值。
- 方法中如果含有run字样就意味这个组合的方式是runnable,即忽略一个计算的结果,仅仅等待前一个计算完成以后才开始下一个计算
大概总结一下就是这样一个表格:
| then | both | either | |
|---|---|---|---|
| apply | thenApply | thenCombine | applyToEither |
| accept | thenAccept | thenAcceptBoth | acceptEither |
| run | thenRun | thenAfterBoth | runAfterEither |
这就有了基础的9个方法,同时CompletionStage还拥有这些个方法的async版本:比如thenCombineAsync
public <U,V> CompletableFuture<V> thenCombine(
CompletionStage<? extends U> other,
BiFunction<? super T,? super U,? extends V> fn) {
return biApplyStage(null, other, fn);
}
public <U,V> CompletableFuture<V> thenCombineAsync(
CompletionStage<? extends U> other,
BiFunction<? super T,? super U,? extends V> fn) {
return biApplyStage(asyncPool, other, fn);
}
较代码上来看他们的差别就主要在于asyncPool的使用:
private static final boolean useCommonPool =
(ForkJoinPool.getCommonPoolParallelism() > 1);
/**
* Default executor -- ForkJoinPool.commonPool() unless it cannot
* support parallelism.
*/
private static final Executor asyncPool = useCommonPool ?
ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
可以看到如果说在有Async后缀的版本并且不传 Executor的话,方法就会使用默认的ForkJoinPool#commonPool
- 没有 Async 后缀的版本,后一个计算将在前一个计算完成时紧接着进行。这里有一个非常不幸的不确定性,如果前一个计算在 thread-1 上进行,执行组合方法(例如
thenApply),如果前一个计算在执行组合方法时还没有完成,那么完成后后一个计算就在 thread-1 上紧接着进行。如果在执行组合方法时前一个计算已经完成,那么后一个计算将在执行组合方法的线程上执行。 - 有 Async 后缀但不传递
Executor的版本,后一个计算将在前一个计算完成时由默认的Executor执行。这个Executor会是ForkJoinPool#commonPool,但如果这个线程池不支持并发也就是并发度不大于 1 的话,就会为每个计算都起一个新的 Thread 来执行。 - 有 Async 后缀且传递
Executor的版本,跟不传递的版本类似,只是使用用户传递的Executor来执行。
CompletableFuture中的异常处理
失败的异步处理这里有三个接口:
public CompletionStage<T> whenComplete
(BiConsumer<? super T, ? super Throwable> action);
public <U> CompletionStage<U> handle
(BiFunction<? super T, Throwable, ? extends U> fn);
public CompletionStage<T> exceptionally
(Function<Throwable, ? extends T> fn);
描述性地说,whenComplete 在方法结束后插入一个动作,并返回原来的计算结果;handle 在方法结束后插入一个函数,返回一个可能是新的类型的计算结果;exceptionally 针对失败的计算的具体异常,返回一个和原先计算结果类型相符合的值,其语义类似于从异常中恢复。
这里使用一个实例代码来看看具体的运行结果
public static void main(String[] args) throws Exception {
// 创建异步执行任务:
CompletableFuture<Double> cf = CompletableFuture.supplyAsync(CompletableFutureTest::fetchPrice);
// 如果执行成功:
cf.thenAccept((result) -> {
System.out.println("price: " + result);
});
// 如果执行异常:
cf.exceptionally((e) -> {
System.out.println("exceptionally: " + e);
return null;
});
cf.whenComplete((result,e) -> {
System.out.println("Complete: " + e);
});
cf.handle((result,e) -> {
System.out.println("handle: " + e);
return null;
});
// 主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:
Thread.sleep(200);
}
static Double fetchPrice() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
if (Math.random() < 300) {
throw new RuntimeException("fetch price failed!");
}
return 5 + Math.random() * 20;
}
正常的结果:
异常的结果:
可以看到
exceptionally是专门对异常进行处理的,而另两个兼顾了返回的结果
runAsyc 和supplyAync 测试代码:
public static void main(String[] args) {
try {
CompletableFuture<Void> f1 = CompletableFuture.runAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第一个异步任务");
});
f1.get();
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第二个异步任务");
return "second";
});
CompletableFuture.allOf(f1,f2).join();
System.out.println("第一个异步任务返回"+f1.get());
System.out.println("第二个异步任务返回"+f2.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("CompletableFuture Test runAsync");
}