Future&CompletionService&CompletableFuture使用(1)
1. Future与Callable
Future和Callable一般是配合使用起来的。相比Runnable,Callable多了返回值,可以返回结果/异常,由Future对象接收。
1.1 执行流程
线程池submit一个Callable任务,会立即有一个Future对象返回,但是此时的Future对象为空,当Callable任务执行结束之后会把结果/异常放入Future对象中。
1.2Future&Callable的使用
@Slf4j
public class CompletionDemo {
// 日志
private static final Logger LOGGER = LoggerFactory.getLogger(CompletionDemo.class);
// 线程池
private static final ExecutorService executor = Executors.newFixedThreadPool(2);
public static void main(String[] args) throws ExecutionException, InterruptedException {
futureDemo1();
}
private static void futureDemo1() throws InterruptedException, ExecutionException {
try {
// executor.execute() 无返回值参数为Runnable ; executor.submit() 有返回值参数为Callable
// 使用submit()会立即返回一个空的Future对象,此时计算可能还未完成,等计算完成后会把返回结果补充到Future对象中
Future<Double> future = executor.submit(() -> {
sleep(10);
// exception();
return 10.0;
});
int i = 0;
//判断是否完成任务
while (!future.isDone() && i < 3 ) {
sleep(2);
i ++ ;
LOGGER.info( "Thread1:{} \t FutureResult:{}", Thread.currentThread().getName(), "阿巴阿巴");
}
//获取返回值,如果自任务没有完成就阻塞。 如果future抛出了异常,则get()会获取到异常。
LOGGER.info( "Thread2:{} \t FutureResult:{}", Thread.currentThread().getName(), future.get());
LOGGER.info( "Thread3:{} \t FutureResult:{}", Thread.currentThread().getName(), "呜呜呜呜");
} finally {
executor.shutdown();
}
}
private static void sleep(Integer i) {
try {
TimeUnit.SECONDS.sleep(i);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
private static void exception() {
throw new RuntimeException("《异常》");
}
}
get()方法获取返回值,如果任务没有完成线程就会被阻塞,如果任务抛出了一场,get()抛出异常。
由此可见Future&Callable的组合有一个很大的局限性就是,如果任务没有完成,使用get()就会被阻塞,而且没有异常的处理方法,只能去在get的时候catch。
2. CompletionService
CompletionService内部实现了一个BlockingQueue,可以将获取的成功的结果放入阻塞队列中,通过调用take()或者poll()可以获取一个已经执行的Future对象。相当于把多个Callable任务返回的Future对象塞入了CompletionService的阻塞队列中。
take和poll的区别:
take()如果队列中没有数据,则线程会wait并释放CPU,而poll()则会直接返回null。
如果查看源码可以看到有一行这样的代码:
// 将执行成功的任务add到队列中
protected void done() { completionQueue.add(task); }
顺便熟悉一下add()、put()和offer()的区别:
三个方法都会将元素插入至队列的尾端但是三者有一定的区别:
add()和offer()类似,如果空间可用可以直接插入元素,如果不可用前者会抛出异常后者会返回false;
put()如果空间不可用会等待空间可用之后put。
Demo
/**
* CompletionService内部是一个阻塞队列
* @throws InterruptedException
* @throws ExecutionException
*/
private static void completionServiceDemo1() throws InterruptedException, ExecutionException {
CompletionService completionService = new ExecutorCompletionService(executor);
completionService.submit(() -> {
LOGGER.info("Thread1:{}", Thread.currentThread().getName());
sleep(10);
return 10;
});
completionService.submit(() -> {
LOGGER.info("Thread2:{}", Thread.currentThread().getName());
sleep(3);
return 3;
});
new Thread(()-> completionService.submit(()->{
LOGGER.info("Thread3:{}", Thread.currentThread().getName());
sleep(15);
return 15;
})).start();
LOGGER.info("FutureResult:{}", completionService.take().get());
LOGGER.info("RUNNING:{}", 1);
LOGGER.info("FutureResult:{}", completionService.take().get());
LOGGER.info("RUNNING:{}", 2);
long start = System.currentTimeMillis();
LOGGER.info("FutureResult:{}", completionService.take().get());
LOGGER.info("WAIT TIME:{}", (System.currentTimeMillis() - start)/1000 );
}
执行结果如下:
[pool-1-thread-2] INFO - Thread2:pool-1-thread-2
[pool-1-thread-1] INFO - Thread1:pool-1-thread-1
[pool-1-thread-2] INFO - Thread3:pool-1-thread-2
[main] INFO - FutureResult:3
[main] INFO - RUNNING:1
[main] INFO - FutureResult:10
[main] INFO - RUNNING:2
[main] INFO - FutureResult:15
[main] INFO - WAIT TIME:8
从上面的demo可以看出CompletionService具有的优势:
-
能够批量异步的执行任务,先执行完成的任务先入队列,不会像Future一样阻塞主线程
-
可以自定义线程池传入参数
3. CompletableFuture
CompletionService可以看作一个Future的队列,本质还是多个已经完成的任务组成的。如果子任务抛出了异常,主任务调用get() ,主任务的线程会因为子任务的异常终止。所以我们有时候期望如果子任务抛出了异常,下一个任务可以接受到异常处理新的任务,而不是终止线程或者阻塞线程。
CompltableFuture实现了Future 接口,在Future的基础上增加了回调、流处理、任务的编排等能力。
3.1.使用CompletableFuture提交任务
CompletableFuture提供了两个方法supplyAsync和runAsync,这两个方法的区别在于supplyAsync相当于executor.submit()方法,是有返回值的异步任务;runAsync相当于executor.execute(),是无返回值的异步任务。
demo略
3.2.CompletableFuture的流式处理
方法:
thenApply与thenApplyAsync
thenAccept与thenAcceptAsync
区别:
thenApply不可以指定线程池,会使用上一个任务的线程或者使用main线程执行执行当前任务;
thenApplyAsyn可以指定线程池(默认线程池是 ForkJoinPool),如果有线程池(n >1)的情况下会开启新的线程当前任务。
thenAccept不可以指定线程池,自身没有返回值(void)
thenAcceptAsync可以指定线程池(默认线程池是 ForkJoinPool),自身没有返回值(void)
源码:
public <U> CompletableFuture<U> thenApply(
Function<? super T,? extends U> fn) {
return uniApplyStage(null, fn);
}
public <U> CompletableFuture<U> thenApplyAsync(
Function<? super T,? extends U> fn) {
return uniApplyStage(asyncPool, fn);
}
public <U> CompletableFuture<U> thenApplyAsync(
Function<? super T,? extends U> fn, Executor executor) {
return uniApplyStage(screenExecutor(executor), fn);
}
public CompletableFuture<Void> thenAccept(Consumer<? super T> action) {
return uniAcceptStage(null, action);
}
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action) {
return uniAcceptStage(asyncPool, action);
}
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action,
Executor executor) {
return uniAcceptStage(screenExecutor(executor), action);
}
demo:
private static void completableFutureThenApplyDemo() throws InterruptedException, ExecutionException {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
LOGGER.info("Thread1:{} ", Thread.currentThread().getName());
// exception();
return 10;
}, executor).thenApply((result) -> {
// thenApply()不会接受异常跳过该方法,如果正常返回可以接受结果。使用上一个任务的线程执行该任务。
LOGGER.info("Thread2:{} \t FutureResult:{}", Thread.currentThread().getName(), result);
// exception();
return 5;
}).thenApplyAsync((result)->{
// thenApplyAsync() 不会接受异常跳过该方法,如果正常返回可以接受结果。可以指定线程池为参数,不一定使用上一个任务的线程执行该任务。
LOGGER.info("Thread3:{} \t FutureResult:{}", Thread.currentThread().getName(), result);
return 4;
}).thenApplyAsync((result)->{
LOGGER.info("Thread4:{} \t FutureResult:{}", Thread.currentThread().getName(), result);
return 3;
}, executor);
LOGGER.info("阿巴阿巴");
// 在thenApply返回之前get()方法阻塞。thenApply抛出异常get()异常也会抛出
LOGGER.info("Thread5:{} \t FutureResult:{}", Thread.currentThread().getName(), future.get());
}
输出:
[pool-1-thread-1] INFO - Thread1:pool-1-thread-1
[main] INFO - 阿巴阿巴
[pool-1-thread-1] INFO - Thread2:pool-1-thread-1 FutureResult:10
[ForkJoinPool.commonPool-worker-1] INFO - Thread3:ForkJoinPool.commonPool-worker-1 FutureResult:5
[pool-1-thread-2] INFO - Thread4:pool-1-thread-2 FutureResult:4
[main] INFO - Thread5:main FutureResult:3
方法: thenRun/thenRunAsync
区别: 略
源码:
public CompletableFuture<Void> thenRun(Runnable action) {
return uniRunStage(null, action);
}
public CompletableFuture<Void> thenRunAsync(Runnable action) {
return uniRunStage(asyncPool, action);
}
public CompletableFuture<Void> thenRunAsync(Runnable action,
Executor executor) {
return uniRunStage(screenExecutor(executor), action);
}
demo
private static void completableFutureThenAcceptAndThenRunDemo() throws InterruptedException, ExecutionException {
CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
LOGGER.info("Thread1:{} ", Thread.currentThread().getName());
// exception();
return 10;
}, executor).thenAccept((result) -> {
// thenAccept()方法 会接受上一个任务的返回值,自身无返回值返回,不会接受异常,使用上一个任务的线程处理该任务
LOGGER.info("Thread2:{} \t FutureResult:{}", Thread.currentThread().getName(), result);
}).thenRun(() -> {
// thenRun()方法 不会接受上一个任务的返回值,自身无返回值返回,使用上一个任务的线程处理该任务
// exception();
LOGGER.info("Thread3:{}", Thread.currentThread().getName());
});
LOGGER.info("阿巴阿巴");
// 抛出异常get()异常也会抛出
LOGGER.info("Thread4:{} \t FutureResult:{}", Thread.currentThread().getName(), future.get());
}
输出
14:07:52.085 [pool-1-thread-1] INFO - Thread1:pool-1-thread-1
14:07:52.085 [main] INFO - 阿巴阿巴
14:07:52.086 [pool-1-thread-1] INFO - Thread2:pool-1-thread-1 FutureResult:10
14:07:52.086 [pool-1-thread-1] INFO - Thread3:pool-1-thread-1
14:07:52.086 [main] INFO - Thread4:main FutureResult:null
方法: exceptionally
exceptionally()方法会接受上一个任务的异常结果,如果上一个线程执行正常则不会调用该方法与上一个任务使用同一个线程
源码:
public CompletableFuture<T> exceptionally(
Function<Throwable, ? extends T> fn) {
return uniExceptionallyStage(fn);
}
demo:
private static void completableFutureExceptionallyDemo() throws InterruptedException, ExecutionException {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
LOGGER.info("Thread1:{} ", Thread.currentThread().getName());
exception();
return 10;
}, executor).exceptionally((exception) -> {
// exceptionally()方法会接受上一个任务的异常结果,非异常不接受,与上一个任务使用同一个线程
LOGGER.info("Thread2:{} \t FutureResult:{}", Thread.currentThread().getName(), exception.getMessage());
// exception();
return 5;
});
LOGGER.info("阿巴阿巴");
// exceptionally抛出异常get()异常也会抛出
LOGGER.info("Thread3:{} \t FutureResult:{}", Thread.currentThread().getName(), future.get());
}
输出:
14:18:25.917 [pool-1-thread-1] INFO - Thread1:pool-1-thread-1
14:18:25.917 [main] INFO - 阿巴阿巴
14:18:25.918 [pool-1-thread-1] INFO - Thread2:pool-1-thread-1 FutureResult:java.lang.RuntimeException: 《异常》
14:18:25.919 [main] INFO - Thread3:main FutureResult:5
方法: whenComplete/handle
区别:
whenComplete本身无返回值,返回上一个任务的CompletableFuture对象。如果上一个对任务抛出了异常,whenComplete会捕获到异常但是其返回值还是上一个任务的CompletableFuture,也就是说在get的时候一样会抛出异常;
handle带有返回值,返回新的CompletableFuture。
demo:
private static void completableFutureWhenCompleteAndHandleDemo() throws InterruptedException, ExecutionException {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
LOGGER.info("Thread1:{} ", Thread.currentThread().getName());
exception();
return 10;
}, executor).whenComplete((result, exception) -> {
// 参数一:上一个任务的正常返回结果,如果上一个任务异常该参数为null
// 参数二:上一个任务的异常返回结果,如果上一个任务正常该参数为null
// handle(var1, var2)与whenComplete类似,区别在于前者可以增加返回值,后端无返回值
LOGGER.info("Thread2:{} \t FutureResult1:{} \t FutureResult2:{}", Thread.currentThread().getName(),
result, exception == null ? null : exception.getMessage());
});
LOGGER.info("阿巴阿巴");
// whenComplete()\上一个任务抛出异常get()异常也会抛出
LOGGER.info("Thread3:{} \t FutureResult:{}", Thread.currentThread().getName(), future.get());
}
输出:
14:41:09.696 [main] INFO - 阿巴阿巴
14:41:09.697 [pool-1-thread-1] INFO - Thread2:pool-1-thread-1 FutureResult1:null FutureResult2:java.lang.RuntimeException: 《异常》
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.RuntimeException: 《异常》
Caused by: java.lang.RuntimeException: 《异常》
方法: thenCombine / thenAcceptBoth / runAfterBoth
区别:
thenCombine两个任务都成功后执行接收两个任务的结果并自身带有返回值;thenAcceptBoth两个任务都成功后执行接收两个任务的结果,自身无返回值;
runAfterBoth两个任务都成功后执行后执行,无入参,无返回。
demo:
private static void completableFutureCombineDemo() {
CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(() -> {
return 1;
});
CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(() -> {
return 1;
}).thenCombine(f1, (result, exception) -> {
// 只有两个任务都处理完才会执行这个方法
// 参数一:其他任务 参数二:(上一个任务的返回结果,上一个任务的异常返回结果)
return 1;
});
CompletableFuture<Void> f3 = CompletableFuture.supplyAsync(() -> {
return 1;
}).runAfterBoth(f1, () -> {
// 只有两个任务都处理完才会执行这个方法
// runAfterBoth()无返回值 无参数二
});
CompletableFuture<Void> f4 = CompletableFuture.supplyAsync(() -> {
return 1;
}).thenAcceptBoth(f1, (result, exception) -> {
// 只有两个任务都处理完才会执行这个方法
// thenAcceptBoth() 同 thenCombine() 但无返回值
});
}
方法: applyToEither / acceptEither / runAfterEither
区别:
applyToEither两个任务其中一个成功后执行接收两个任务的结果并自身带有返回值;
acceptEither两个任务其中一个成功后执行接收两个任务的结果,自身无返回值;
runAfterEither两个任务其中一个成功后执行后执行,无入参,无返回。
demo: 略,可以参考thenCombine / thenAcceptBoth / runAfterBoth
方法: allOf
demo:
private static void CompletableFutureAllOf() {
ConcurrentHashMap concurrentHashMap = new ConcurrentHashMap(3);
CompletableFuture.allOf(CompletableFuture.runAsync(() -> {
sleep(5);
concurrentHashMap.put("1", Thread.currentThread().getName());
}, executor), CompletableFuture.runAsync(() -> {
sleep(1);
concurrentHashMap.put("2", Thread.currentThread().getName());
}, executor), CompletableFuture.runAsync(() -> {
sleep(7);
concurrentHashMap.put("3", Thread.currentThread().getName());
}, executor)).thenRun(()->{
LOGGER.info("thenRun: concurrentHashMap:{}", concurrentHashMap);
});
LOGGER.info("main: concurrentHashMap:{}", concurrentHashMap);
}
输出:
14:55:12.873 [main] INFO - main: concurrentHashMap:{}
14:55:20.881 [pool-1-thread-2] INFO - thenRun: concurrentHashMap:{1=pool-1-thread-1, 2=pool-1-thread-2, 3=pool-1-thread-2}
使用方法:anyOf
demo:
private static void completableFutureAnyOf() {
ConcurrentHashMap concurrentHashMap = new ConcurrentHashMap(3);
CompletableFuture.anyOf(CompletableFuture.runAsync(() -> {
sleep(5);
concurrentHashMap.put("1", Thread.currentThread().getName());
}, executor), CompletableFuture.runAsync(() -> {
sleep(1);
concurrentHashMap.put("2", Thread.currentThread().getName());
}, executor), CompletableFuture.runAsync(() -> {
sleep(7);
concurrentHashMap.put("3", Thread.currentThread().getName());
}, executor)).thenRun(()->{
LOGGER.info("thenRun: concurrentHashMap:{}", concurrentHashMap);
});
LOGGER.info("main: concurrentHashMap:{}", concurrentHashMap);
}
输出:
14:57:26.688 [main] INFO - main: concurrentHashMap:{}
14:57:27.690 [pool-1-thread-2] INFO - thenRun: concurrentHashMap:{2=pool-1-thread-2}