并发编程从入门到放弃系列开始和结束(七)
public static void main(String[] args) throws Exception {
// testInvokeAny();
testCompletionService();
}
private static void testCompletionService() {
CompletionService<String> completionService = new ExecutorCompletionService(executorService);
List<Future> taskList = new LinkedList<>();
for (int i = 0; i < TOTAL; i++) {
taskList.add(completionService.submit(new TestTask(i)));
}
String value = null;
try {
for (int i = 0; i < TOTAL; i++) {
value = completionService.take().get();
if (value != null) {
System.out.println("get value = " + value);
break;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
taskList.forEach(task -> {
task.cancel(true);
});
}
executorService.shutdown();
}
//输出
task-4 sleep 5006 Ms
task-1 sleep 4114 Ms
task-2 sleep 4865 Ms
task-5 sleep 1592 Ms
task-3 sleep 6190 Ms
task-7 sleep 2482 Ms
task-8 sleep 9405 Ms
task-9 sleep 8798 Ms
task-6 sleep 2040 Ms
task-0 sleep 2111 Ms
get value = task-5
效果是一样的,我们只是实现了一个简化版的 invokeAny 功能,使用起来也挺简单的。
实现原理也挺简单的,哪个任务先完成,就把他丢到阻塞队列里,这样取任务结果的时候直接从队列里拿,肯定是拿到最新的那一个。
异步结果
通常,我们都会用 FutureTask 来获取线程异步执行的结果,基于 AQS 实现。
这个没有说太多的必要,看看几个方法就行了。
public V get();
public V get(long timeout, TimeUnit unit);
public boolean cancel(boolean mayInterruptIfRunning);
get 会阻塞的获取线程异步执行的结果,一般不建议直接使用,最好是使用带超时时间的 get 方法。
我们可以通过 cancel 方法去尝试取消任务的执行,参数代表是否支持中断,如果任务未执行,那么可以直接取消,如果任务执行中,使用 cancel(true) 会尝试中断任务。
CompletableFuture
之前我们都在使用 Future,要么只能用 get 方法阻塞,要么就用 isDone 来判断,JDK1.8 之后新增了 CompletableFuture 用于异步编程,它针对 Future 的功能增加了回调能力,可以帮助我们简化异步编程。
CompletableFuture 主要包含四个静态方法去创建对象,主要区别在于 supplyAsync 返回计算结果,runAsync 不返回,另外两个方法则是可以指定线程池,如果不指定线程池则默认使用 ForkJoinPool,默认线程数为CPU核数。
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier);
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor);
public static CompletableFuture<Void> runAsync(Runnable runnable);
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor);
下面看看他的那些恶心人的几十个方法,我估计能疯。
串行
串行就不用解释了,A->B->C 按照顺序执行,下一个任务必须等上一个任务执行完成才可以。
主要包含 thenApply、thenAccept、thenRun 和 thenCompose,以及他们对应的带 async 的异步方法。
为了方便记忆我们要记住,有 apply 的有传参有返回值,带 accept 的有传参但是没有返回值,带 run 的啥也没有,带 compose 的会返回一个新的 CompletableFuture 实例。
public static void main(String[] args) throws Exception {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread() + "工作完成");
return "supplyAsync";
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
CompletableFuture newFuture = future.thenApply((ret) -> {
System.out.println(Thread.currentThread() + "thenApply=>" + ret);
return "thenApply";
}).thenAccept((ret) -> {
System.out.println(Thread.currentThread() + "thenAccept=>" + ret);
}).thenRun(() -> {
System.out.println(Thread.currentThread() + "thenRun");
});
CompletableFuture<String> composeFuture = future.thenCompose((ret) -> {
System.out.println(Thread.currentThread() + "thenCompose=>" + ret);
return CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread() + "thenCompose工作完成");
return "thenCompose";
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
});
System.out.println(future.get());
System.out.println(newFuture.get());
System.out.println(composeFuture.get());
}
//输出
Thread[ForkJoinPool.commonPool-worker-9,5,main]工作完成
Thread[ForkJoinPool.commonPool-worker-9,5,main]thenCompose=>supplyAsync
Thread[main,5,main]thenApply=>supplyAsync
Thread[main,5,main]thenAccept=>thenApply
Thread[main,5,main]thenRun
supplyAsync
null
Thread[ForkJoinPool.commonPool-worker-2,5,main]thenCompose工作完成
thenCompose
AND 聚合
这个意思是下一个任务执行必须等前两个任务完成可以。
主要包含 thenCombine、thenAcceptBoth、runAfterBoth ,以及他们对应的带 async 的异步方法,区别和上面一样。
public static void main(String[] args) throws Exception {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread() + "A工作完成");
return "A";
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
System.out.println(Thread.currentThread() + "B工作完成");
return "B";
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
CompletableFuture newFuture = future.thenCombine(future2, (ret1, ret2) -> {
System.out.println(Thread.currentThread() + "thenCombine=>" + ret1 + "," + ret2);
return "thenCombine";
}).thenAcceptBoth(future2, (ret1, ret2) -> {
System.out.println(Thread.currentThread() + "thenAcceptBoth=>" + ret1 + "," + ret2);
}).runAfterBoth(future2, () -> {
System.out.println(Thread.currentThread() + "runAfterBoth");
});
System.out.println(future.get());
System.out.println(future2.get());
System.out.println(newFuture.get());
}
//输出
Thread[ForkJoinPool.commonPool-worker-9,5,main]A工作完成
A
Thread[ForkJoinPool.commonPool-worker-2,5,main]B工作完成
B
Thread[ForkJoinPool.commonPool-worker-2,5,main]thenCombine=>A,B
Thread[ForkJoinPool.commonPool-worker-2,5,main]thenAcceptBoth=>thenCombine,B
Thread[ForkJoinPool.commonPool-worker-2,5,main]runAfterBoth
null
Or 聚合
Or 聚合代表只要多个任务中有一个完成了,就可以继续下面的任务。
主要包含 applyToEither、acceptEither、runAfterEither ,以及他们对应的带 async 的异步方法,区别和上面一样,不再举例了。
回调/异常处理
whenComplete、handle 代表执行完成的回调,一定会执行,exceptionally 则是任务执行发生异常的回调。
public static void main(String[] args) throws Exception {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
int a = 1 / 0;
return "success";
} catch (Exception e) {
throw new RuntimeException(e);
}
});
CompletableFuture newFuture = future.handle((ret, exception) -> {
System.out.println(Thread.currentThread() + "handle exception=>" + exception.getMessage());
return "handle";
});
future.whenComplete((ret, exception) -> {
System.out.println(Thread.currentThread() + "whenComplete exception=>" + exception.getMessage());
});
CompletableFuture exceptionFuture = future.exceptionally((e) -> {
System.out.println(Thread.currentThread() + "exceptionally exception=>" + e.getMessage());
return "exception";
});
System.out.println("task future = " + future.get());
System.out.println("handle future = " + newFuture.get());
System.out.println("exception future = " + exceptionFuture.get());
}
//输出
Thread[ForkJoinPool.commonPool-worker-9,5,main]exceptionally exception=>java.lang.RuntimeException: java.lang.ArithmeticException: / by zero
Thread[main,5,main]whenComplete exception=>java.lang.RuntimeException: java.lang.ArithmeticException: / by zero
Thread[ForkJoinPool.commonPool-worker-9,5,main]handle exception=>java.lang.RuntimeException: java.lang.ArithmeticException: / by zero
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.RuntimeException: java.lang.ArithmeticException: / by zero
at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357)
at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1908)
at com.example.demo.CompletableFutureTest3.main(CompletableFutureTest3.java:31)
Caused by: java.lang.RuntimeException: java.lang.ArithmeticException: / by zero
at com.example.demo.CompletableFutureTest3.lambda$main$0(CompletableFutureTest3.java:13)
at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1604)
at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1596)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1067)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1703)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:172)
Caused by: java.lang.ArithmeticException: / by zero
at com.example.demo.CompletableFutureTest3.lambda$main$0(CompletableFutureTest3.java:10)
... 6 more
文章转自公众号:艾小仙