异步完成和连接任务

Java 9并发编程指南 目录

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类非常灵活且功能强大。在本节中,学习如何使用此类管理不同的任务,范例将按照如下顺序图指令进行执行:

pics/03_06.jpg

首先,创建一个任务生成起始数。接下来的任务是使用这个数字生成一列随机数。然后,执行三个并行任务:

  1. 步骤1将在一列随机数中计算离1000最近的数。
  2. 步骤2将在一列随机数中计算最大的数。
  3. 步骤3将在一列随机数中计算最大值和最小值的平均数。

准备工作

本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。

实现过程

通过如下步骤完成范例:

  1. 首先,实现本范例中的附属任务,创建名为SeedGenerator类,实现Runnable接口,CompletableFuture对象作为属性,并且在类构造函数中初始化:

    public class SeedGenerator implements Runnable {
    
    	private CompletableFuture<Integer> resultCommunicator;
    	public SeedGenerator(CompletableFuture<Integer> completable) {
    		this.resultCommunicator = completable;
    	}
    
  2. 然后,实现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);
    	}
    
  3. 创建名为NumberListGenerator的类,实现具有List数据类型参数化的Supplier接口,意味着Supplier接口提供的get()方法将返回长整型数字队列。此类包含一个整型私有属性,在构造函数中初始化:

    public class NumberListGenerator implements Supplier<List<Long>>{
    
    	private final int size;
    	public NumberListGenerator(int size) {
    		this.size = size;
    	}
    
  4. 然后,实现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;
    	}
    
  5. 最后,创建名为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;
    	}
    }
    
  6. 现在实现Main类和main()方法:

    public class Main {
    	public static void main(String[] args) {
    
  7. 首先,创建CompletableFuture对象和SeedGenerator任务,作为Thread执行此任务:

    		System.out.printf("Main : Start\n");
    		CompletableFuture<Integer> seedFuture = new CompletableFuture<>();
    		Thread seedThread = new Thread(new SeedGenerator(seedFuture));
    		seedThread.start();
    
  8. 然后,使用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);
    
  9. 现在创建另一个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);
    
  10. 然后,配置三个并行任务,基于之前任务生成的数据列进行计算。只有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);
    
  11. 使用CompletableFuture类的allOf()静态方法等待三个并行步骤的结束:

    		System.out.printf("Main : Waiting for the end of the three steps\n");
    		CompletableFuture<Void> waitFuture = CompletableFuture.allOf(step1Future, write2Future, step3Future);
    
  12. 输出最后的步骤信息到控制台:

    		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对象的结束。

下图显示本范例在控制台输出的执行结果,能够看到任务是如何按照管理秩序执行:

pics/03_07.jpg

扩展学习

在本节的范例中,我们用到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完成值相同类型的值,如果完成对象,返回完成值,佛祖额返回作为参数传递的值。

更多关注

  • 第四章“线程执行器”中“创建线程执行器并控制其被拒任务”和“执行器中运行返回结果的任务”小节
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值