Java 8并发API在CompletableFuture类中创造一种新的同步机制,通过类实现的Future对象和CompletionStage接口提供如下两个特性:
- 针对Future对象,CompletableFuture对象将在未来某时刻返回结果
- 针对CompletionStage对象,当至少一个CompletableFuture对象结束后,可以执行更多的异步任务
可通过不同的方式使用CompletableFuture类:
- 可以明确地创建CompletableFuture对象,作为任务之间的同步点进行使用。其中一个任务使用complete()方法,建立通过CompletableFuture返回的值。其它任务将使用get()或join()方法等待这个值。
- 可以使用CompletableFuture类的静态方法,通过runAsync()和supplyAsync()方法执行Runnable或者Supplier。当这些任务结束执行时,方法将返回即将完成的CompletableFuture对象。第二种情形中,Supplier返回的值将是CompletableFuture的完成值。
- 当至少一个CompletableFuture对象结束后,可以指定其它线程在异步方式下执行。此任务能够实现Runnable,Function,Consumer,BiConsumer接口。
这些特性使得CompletableFuture类非常灵活且功能强大。在本节中,学习如何使用此类管理不同的任务,范例将按照如下顺序图指令进行执行:
首先,创建一个任务生成起始数。接下来的任务是使用这个数字生成一列随机数。然后,执行三个并行任务:
- 步骤1将在一列随机数中计算离1000最近的数。
- 步骤2将在一列随机数中计算最大的数。
- 步骤3将在一列随机数中计算最大值和最小值的平均数。
准备工作
本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。
实现过程
通过如下步骤完成范例:
-
首先,实现本范例中的附属任务,创建名为SeedGenerator类,实现Runnable接口,CompletableFuture对象作为属性,并且在类构造函数中初始化:
public class SeedGenerator implements Runnable { private CompletableFuture<Integer> resultCommunicator; public SeedGenerator(CompletableFuture<Integer> completable) { this.resultCommunicator = completable; }
-
然后,实现run()方法,休眠当前线程5秒钟(模拟一个长时间操作),计算一个1到10之间的随机数,然后使用resultCommunicator对象的complete()方法完成CompletableFuture:
@Override public void run() { System.out.printf("SeedGenerator : Generating seed...\n"); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } int seed = (int) Math.rint(Math.random() * 10); System.out.printf("SeedGenerator : Seed generated : %d\n", seed); resultCommunicator.complete(seed); }
-
创建名为NumberListGenerator的类,实现具有List数据类型参数化的Supplier接口,意味着Supplier接口提供的get()方法将返回长整型数字队列。此类包含一个整型私有属性,在构造函数中初始化:
public class NumberListGenerator implements Supplier<List<Long>>{ private final int size; public NumberListGenerator(int size) { this.size = size; }
-
然后,实现get()方法,返回长整型随机数字的列表:
@Override public List<Long> get() { List<Long> ret = new ArrayList<>(); System.out.printf("%s : NumberListGenerator : Start\n", Thread.currentThread().getName()); for (int i = 0 ; i < size * 1000000 ; i ++) { long number = Math.round(Math.random() * Long.MAX_VALUE); ret.add(number); } System.out.printf("%s : NumberListGenerator : End\n", Thread.currentThread().getName()); return ret; }
-
最后,创建名为NumberSelector的类,实现具有List和Long数据类型参数化的Function接口,意味着Function接口提供的apply()方法将返回长整型数字队列和Long数字:
public class NumberSelector implements Function<List<Long>, Long>{ @Override public Long apply(List<Long> list) { System.out.printf("%s : Step 3 : Start\n", Thread.currentThread().getName()); long max = list.stream().max(Long::compare).get(); long min = list.stream().min(Long::compare).get(); long result = ( max + min ) / 2; System.out.printf("%s : Step3 : Result - %d\n", Thread.currentThread().getName(), result); return result; } }
-
现在实现Main类和main()方法:
public class Main { public static void main(String[] args) {
-
首先,创建CompletableFuture对象和SeedGenerator任务,作为Thread执行此任务:
System.out.printf("Main : Start\n"); CompletableFuture<Integer> seedFuture = new CompletableFuture<>(); Thread seedThread = new Thread(new SeedGenerator(seedFuture)); seedThread.start();
-
然后,使用CompletableFuture对象的get()方法,等待SeedGenerator任务生成的起始数:
System.out.printf("Main : Getting the seed\n"); int seed = 0; try { seed = seedFuture.get(); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } System.out.printf("Main : The seed is : %d\n", seed);
-
现在创建另一个CompletableFuture对象来控制NumberListGenerator任务的执行,但这里使用静态方法supplyAsync():
System.out.printf("Main : Launching the list of numbers generator\n"); NumberListGenerator task = new NumberListGenerator(seed); CompletableFuture<List<Long>> startFuture = CompletableFuture.supplyAsync(task);
-
然后,配置三个并行任务,基于之前任务生成的数据列进行计算。只有NumberListGenerator任务已经完成执行之后,这三个步骤才能开始运行。所以我们使用上一步骤中生成的CompletableFuture对象以及thenApplyAsync()方法来配置这些任务。前两个步骤以函数方式实现,第三个是NumberSelector类中的一个对象:
System.out.printf("Main : Launching step 1\n"); CompletableFuture<Long> step1Future = startFuture.thenApplyAsync(list -> { System.out.printf("%s : Step 1 :Start \n", Thread.currentThread().getName()); long selected = 0; long selectedDistance = Long.MAX_VALUE; long distance; for (Long number : list) { distance = Math.abs(number - 1000); if (distance < selectedDistance) { selected = number; selectedDistance = distance; } } System.out.printf("%s : Step 1 : Result - %d\n", Thread.currentThread().getName(), selected); return selected; }); System.out.printf("Main : Launching step 2\n"); CompletableFuture<Long> step2Future = startFuture.thenApplyAsync(list -> list.stream().max(Long::compare).get()); CompletableFuture<Void> write2Future = step2Future.thenAccept(selected -> { System.out.printf("%s : Step2 : Result - %d\n", Thread.currentThread().getName(), selected); }); System.out.printf("Main : Launching step 3\n"); NumberSelector numberSelector = new NumberSelector(); CompletableFuture<Long> step3Future = startFuture.thenApplyAsync(numberSelector);
-
使用CompletableFuture类的allOf()静态方法等待三个并行步骤的结束:
System.out.printf("Main : Waiting for the end of the three steps\n"); CompletableFuture<Void> waitFuture = CompletableFuture.allOf(step1Future, write2Future, step3Future);
-
输出最后的步骤信息到控制台:
CompletableFuture<Void> finalFuture =waitFuture.thenAcceptAsync((param) -> { System.out.printf("Main : The CompletableFuture example has been completed."); }); finalFuture.join();
工作原理
使用CompletableFuture对象,有两个主要目的:
- 等待在将来将要产生的值或者事件(创建对象并且使用comple()和get()或者join()方法)。
- 为了管理一组任务按照确定的顺序执行,这样当部分任务已经结束后,其它任务才能开始执行。
本范例中全部使用的CompletableFuture类。首先,创建这个类的接口,作为参数传递给SeedGenerator任务。此任务使用complete()方法传递计算值,以及main()方法使用get()方法得到此值。get()方法勖勉当前线程,直到已经完成CompletableFuture。
然后,使用supplyAsync()方法生成CompletableFuture对象,此方法将Supplier的接口实现作为参数接收。此接口提供必须返回值的get()方法,supplyAsync()方法返回当get()方法结束执行时才能完成的CompletableFuture,完成值即为supplyAsync()方法的返回值。ForkJoinPool中的一个任务执行返回的CompletableFuture对象,此任务返回静态方法commonPool()。
接下来,使用thenApplyAsync()方法连接任务,在CompletableFuture对象里调用,并且必须将Function接口实现作为参数传递,能够在代码中使用函数风格或者独立对象直接表达出来。一个强大的特性是CompletableFuture生成的值能够作为参数传递给Function。也就是说,在范例中,三个步骤均接接收到作为参数的随机数列表。ForkJoinPool中的一个任务执行返回的CompletableFuture对象,此任务返回静态方法commonPool()。
最后,使用CompletableFuture类的allOf()静态方法等到不同任务的终止。这个方法接收不同的CompletableFuture对象列表,当所有的作为参数传递的CompletableFuture类完成时,返回将要结束的CompletableFuture类。将thenAcceptAsync()方法作为另一种同步任务的方式使用,是因为当用来调用此方法的CompletableFuture对象完成时,此方法将被默认执行器运行的Consumer对象作为参数接收。最终,使用join()方法等待最后一个CompletableFuture对象的结束。
下图显示本范例在控制台输出的执行结果,能够看到任务是如何按照管理秩序执行:
扩展学习
在本节的范例中,我们用到CompletableFuture类的complete()、get()、join()、supplyAsync()、thenApplyAsync()、thenAcceptAsync()和allOf()方法。然而,此类还提供了很多有用的方法让它变得更加灵活且功能强大,如下所示:
- 完成CompletableFuture对象的方法:除了complete()方法,CompletableFuture类提供如下三个方法:
- cancel():完成CompletableFuture,包括CancellationException异常。
- completeAsync():完成CompletableFuture,包括将Supplier对象作为参数传递的结果。Supplier对象在不同的线程中通过默认执行器运行。
- completeExceptionally():完成CompletableFuture,将异常作为参数传递。
- 执行任务的方法:除了supplyAsync()方法,CompletableFuture类提供如下方法:
- runAsync():CompletableFuture类中的静态方法,返回CompletableFuture对象。当Runnable作为参数传递结束执行时,此对象将被完成,包括一个空结果。
- 同步不同任务执行的方法:除了allOf(),thenAcceptAsync()和thenApplyAsync()方法,CompletableFuture类还提供如下同步任务执行的方法:
- anyOf():CompletableFuture类中的静态方法,接收CompletableFuture对象列为参数并返回一个新的CompletableFuture对象。此对象将被完成,包括第一个完成的CompletableFuture参数的结果。
- runAfterBothAsync():此方法将CompletionStage和Runnable接口作为参数接收,并且返回一个新的CompletableFuture对象。当CompletableFuture(用来调用)和CompletionStage(作为参数传递)完成时,Runnable对象通过默认执行器运行,然后完成返回的CompletableFuture对象。
- runAfterEitherAsync():此方法与上一个类似,不同点是在两者(CompletableFuture或者CompletionStage)中的任意一个完成之后,才能执行Runnable接口。
- thenAcceptBothAsync():此方法将CompletionStage和BiConsumer对象作为参数接收,并且将CompletableFuture作为参数返回。当CompletableFuture(用来调用)和CompletionStage(作为参数传递)、BiConsumer通过默认执行器运行时,它将两个CompletionStage的结果作为参数接收,但不返回任何值。当BiConsumer结束执行时,返回的CompletableFuture类完成且不带结果。
- thenCombineAsync():此方法将CompletionStage和Runnable接口作为参数接收,并且返回一个新的CompletableFuture对象。当CompletableFuture(用来调用)和CompletionStage(作为参数传递)完成时,执行BiFunction对象,此方法接收两个对象的完成值,并且返回新的结果,作为返回的CompletableFuture类的完成值。
- thenComposeAsync():此方法与thenApplyAsync()方法功能类似,但当提供的函数也返回CompletableFuture时会很有用。
- thenRunAsync():此方法与thenAcceptAsync()方法功能类似,不过它将Runnable接口作为参数接收,而不是Consumer对象。
- 包含完成值的方法:除了get()和join()方法,CompletableFuture对象还提供如下获得完成值的方法:
- getRow():此方法接收与CompletableFuture完成值相同类型的值,如果完成对象,返回完成值,佛祖额返回作为参数传递的值。
更多关注
- 第四章“线程执行器”中“创建线程执行器并控制其被拒任务”和“执行器中运行返回结果的任务”小节