Java8新特性 - 组合式异步编程

1. Future接口

1.1 基本使用

基本介绍
Future接口的设计初衷是对将来某个时刻会发生的结果进行建模,它建模了一种异步计算,返回一个执行运算结果的引用,当运算结束后,这个引用被返回给调用方

基本使用
只需要将耗时的操作封装在一个Callable对 象中,再将它提交给ExecutorService

ExecutorService executor = Executors.newCachedThreadPool();  
Future<Double> future = executor.submit(new Callable<Double>() {          
	public Double call() {              
		return doSomeLongComputation();          
	}
}); 
doSomethingElse();  
try {     
	Double result = future.get(1, TimeUnit.SECONDS);  
} catch (ExecutionException ee) {      
	// 计算抛出一个异常 
} catch (InterruptedException ie) {     
	// 当前线程在等待过程中被中断 
} catch (TimeoutException te) {     
	// 在Future对象完成之前超过已过期 
} 

这种编程方式让你的线程可以在ExecutorService以并发方式调 用另一个线程执行耗时操作的同时,去执行一些其他的任务。接着,如果你已经运行到没有异步 操作的结果就无法继续任何有意义的工作时,可以调用它的get方法去获取操作的结果。如果操 作已经完成,该方法会立刻返回操作的结果,否则它会阻塞你的线程,直到操作完成,返回相应 的结果

1.2 Future接口的局限性

使用Future接口我很难表述Future结果之间的依赖性,比如下面这些操作

  • 将两个异步计算合并为一个——这两个异步计算之间相互独立,同时第二个又依赖于第 一个的结果
  • 等待Future集合中的所有任务都完成
  • 仅等待Future集合中快结束的任务完成(有可能因为它们试图通过不同的方式计算同 一个值),并返回它的结果
  • 通过编程方式完成一个Future任务的执行

新的CompletableFuture类(它实现了Future接口)利用Java 8 的新特性,使用Lambda表达式和流水线的特性以更直观的方式将上述需求都变为可能

可以把 CompletableFuture和Future的关系就看作Stream和Collection的关系一样,也就是说对于CompletableFuture也可以使用流操作的方式进行操作

2 使用CompletableFuture

2.1 使用构造函数构建异步应用

构造函数

 /**
  * Creates a new incomplete CompletableFuture.
  */
 public CompletableFuture() {
 }

可以看到他的注释:构造一个不完整的CompletableFuture

需要异步处理的任务:

public Future<Double> doAsync() {      
	CompletableFuture<Double> future = new CompletableFuture<>();     
	new Thread( () -> {                 
		double res = calcuate();//耗时计算操作 
		future.complete(res);      
	}).start();     
	return future;  
}  

主线程:

Future<Double> future = doAsync();  
long invocationTime = ((System.nanoTime() - start) / 1_000_000); System.out.println("Invocation returned after " + invocationTime                                                  + " msecs"); 
//在执行耗时计算的时候执行更多任务
 doSomethingElse(); 
try {     
	double res = future.get();      
	System.out.printf("res is %.2f%n", res); 
} catch (Exception e) {     
	throw new RuntimeException(e);  
} 
long retrievalTime = ((System.nanoTime() - start) / 1_000_000); 
System.out.println("res returned after " + retrievalTime + " msecs"); 

处理异常
在上面的代码中入如果耗时的计算任务发生了错误,这种情况下用于提示错误的异常会被限制 在试图进行复杂计算的当前线程的范围内,终会杀死该线程,而这会导致主线程等待get方法返回结果的客户端永久地被阻塞,对于这个问题有两种解决方法

1. 客户端可以使用重载版本的get方法,它使用一个超时参数来避免发生这样的情况
使用 这种方法至少能防止程序永久地等待下去,超时发生时,程序会得到通知发生了Timeout- Exception。但是这样操作,你不会有机会发现进行复杂计算的线程内到底发生了什么问题才引发了这样的失效

2. 使用 CompletableFuture的completeExceptionally方法将导致CompletableFuture内发生问 题的异常抛出

public Future<Double> doAsync() {      
	CompletableFuture<Double> future = new CompletableFuture<>();     
	new Thread( () -> {    
		try{             
			double res = calcuate();//耗时计算操作 
			future.complete(res);    
		}catch(Exception e){
			futurePrice.completeExceptionally(ex); 
		}  
	}).start();     
	return future;  
}  

这样客户端就能感知到计算线程的异常

再回到构造函数
之前说过这个方法会构造一个不完整的CompletableFuture,不完整指的就是我们需要自己手动使用complete()设置异步线程可能返回的值,以及使用completeExceptionally() 向上抛出异常

2.2 使用工厂方法创建CompletableFuture

CompletableFuture提供了四个静态方法用来创建CompletableFuture对象:

//异步操作无返回值
public static CompletableFuture<Void>   runAsync(Runnable runnable)
//使用指定线程池
public static CompletableFuture<Void>   runAsync(Runnable runnable, Executor executor)
//异步操作有返回值
public static <U> CompletableFuture<U>  supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U>  supplyAsync(Supplier<U> supplier, Executor executor)
  • Asynsc表示异步,而supplyAsync与runAsync不同在与前者异步返回一个结果,后者是void,这点通过看Runnable参数也是显而易见的

  • 第二个函数第二个参数表示是用我们自己创建的线程池,否则采用默认的ForkJoinPool.commonPool()作为它的线程池

CompletableFuture<String> future = CompletableFuture.supplyAsync(()->{
	return "hello world";
});
System.out.println(future.get());  //阻塞的获取结果  ''helllo world"

2.3 主动计算

获取结果
对于异步的CompletableFuture有以下四个方法获取结果

public T  get()
//设置超时时间
public T    get(long timeout, TimeUnit unit)
//如果结果当前已经计算完则返回结果或者抛出异常,否则返回给定的valueIfAbsent值,不会阻塞
public T    getNow(T valueIfAbsent)
//join()返回计算的结果或者抛出一个unchecked异常(CompletionException)
public T    join()

关于join和get的不同看函数签名既能看出
在这里插入图片描述
在这里插入图片描述
join()返回计算的结果或者抛出一个unchecked异常(CompletionException)

3. 对多个异步任务进行流水线操作

CompletableFuture 流
CompletableFuture详解
java8 流式处理在CompletableFuture 也得到了完美的体现

3.1 计算结果完成时的处理

当CompletableFuture的计算结果完成,或者抛出异常的时候,我们可以执行特定的操作

//和之前的CompleteableFuture使用相同的线程
public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)
//新选线程
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
//指定线程池
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)
//exceptionally方法返回一个新的CompletableFuture,当原始的CompletableFuture抛出异常的时候,就会触发这个CompletableFuture的计算
public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn)

可以看到Action的类型是BiConsumer<? super T,? super Throwable>,它可以处理正常的计算结果,或者异常情况

方法不以Async结尾,意味着Action使用相同的线程执行,而Async可能会使用其它的线程去执行(如果使用相同的线程池,也可能会被同一个线程选中执行)

这几个方法都会返回CompletableFuture,当Action执行完毕后它的结果返回原始的CompletableFuture的计算结果或者返回异常

public class Main {
    private static Random rand = new Random();
    private static long t = System.currentTimeMillis();
    static int getMoreData() {
        System.out.println("begin to start compute");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("end to start compute. passed " + (System.currentTimeMillis() - t)/1000 + " seconds");
        return rand.nextInt(1000);
    }
    public static void main(String[] args) throws Exception {
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(Main::getMoreData);
        Future<Integer> f = future.whenComplete((v, e) -> {
            System.out.println(v);
            System.out.println(e);
        });
        System.out.println(f.get());
        System.in.read();
    }
}

3.2 转换

如果我想转变计算结果的类型怎么办?
下面一组方法虽然也返回CompletableFuture对象,但是对象的值和原来的CompletableFuture计算的值不同,它的功能相当于将CompletableFuture转换成CompletableFuture

public <U> CompletableFuture<U> handle(BiFunction<? super T,Throwable,? extends U> fn)
public <U> CompletableFuture<U> handleAsync(BiFunction<? super T,Throwable,? extends U> fn)
public <U> CompletableFuture<U> handleAsync(BiFunction<? super T,Throwable,? extends U> fn, Executor executor)

可以看到handle会传递异常,也有下面的操作忽略对异常的处理

public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    return 100;
});
CompletableFuture<String> f =  future.thenApplyAsync(i -> i * 10).thenApply(i -> i.toString());
System.out.println(f.get()); //"1000"

它们与handle方法的区别在于handle方法会处理正常计算值和异常,因此它可以屏蔽异常,避免异常继续抛出。而thenApply方法只是用来处理正常值,因此一旦有异常就会抛出。

3.3 纯消费

上面的方法是当计算完成的时候,会生成新的计算结果(thenApply, handle),或者返回同样的计算结果whenComplete
CompletableFuture还提供了一种处理结果的方法,只对结果执行Action,而不返回新的计算值,因此计算值为Void

public CompletableFuture<Void> 	thenAccept(Consumer<? super T> action)
public CompletableFuture<Void> 	thenAcceptAsync(Consumer<? super T> action)
public CompletableFuture<Void> 	thenAcceptAsync(Consumer<? super T> action, Executor executor)

看它的参数类型也显而易见,它们是函数式接口Consumer,这个接口只有输入,没有返回值

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    return 100;
});
CompletableFuture<Void> f =  future.thenAccept(System.out::println);
System.out.println(f.get());

3.4 组合

如果我想组合两个CompleteFuture的结果怎么办?
thenAcceptBoth以及相关方法提供了这样功能,当两个CompletionStage都正常完成计算的时候,就会执行提供的action,它用来组合另外一个异步的结果。
runAfterBoth是当两个CompletionStage都正常完成计算的时候,执行一个Runnable,这个Runnable并不使用计算的结果

public <U> CompletableFuture<Void> 	thenAcceptBoth(CompletionStage<? extends U> other, BiConsumer<? super T,? super U> action)
public <U> CompletableFuture<Void> 	thenAcceptBothAsync(CompletionStage<? extends U> other, BiConsumer<? super T,? super U> action)
public <U> CompletableFuture<Void> 	thenAcceptBothAsync(CompletionStage<? extends U> other, BiConsumer<? super T,? super U> action, Executor executor)
public     CompletableFuture<Void> 	runAfterBoth(CompletionStage<?> other,  Runnable action)

例子如下

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    return 100;
});
CompletableFuture<Void> f =  future.thenAcceptBoth(CompletableFuture.completedFuture(10), (x, y) -> System.out.println(x * y));
System.out.println(f.get());

更彻底地,下面一组方法当计算完成的时候会执行一个Runnable,与thenAccept不同,Runnable并不使用CompletableFuture计算的结果

public CompletableFuture<Void> 	thenRun(Runnable action)
public CompletableFuture<Void> 	thenRunAsync(Runnable action)
public CompletableFuture<Void> 	thenRunAsync(Runnable action, Executor executor)

可以看到上面的操作是纯消费,那如果想要有返回值怎么办?

public <U> CompletableFuture<U> thenCompose(Function<? super T,? extends CompletionStage<U>> fn)
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T,? extends CompletionStage<U>> fn)
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T,? extends CompletionStage<U>> fn, Executor executor)

这一组方法接受一个Function作为参数,这个Function的输入是当前的CompletableFuture的计算值,返回结果将是一个新的CompletableFuture,这个新的CompletableFuture会组合原来的CompletableFuture和函数返回的CompletableFuture

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    return 100;
});
CompletableFuture<String> f =  future.thenCompose( i -> {
    return CompletableFuture.supplyAsync(() -> {
        return (i * 10) + "";
    });
});
System.out.println(f.get()); //1000

而下面的一组方法thenCombine用来复合另外一个CompletionStage的结果,两个CompletionStage是并行执行的,它们之间并没有先后依赖顺序,other并不会等待先前的CompletableFuture执行完毕后再执行

public <U,V> CompletableFuture<V> 	thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn)
public <U,V> CompletableFuture<V> 	thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn)
public <U,V> CompletableFuture<V> 	thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn, Executor executor)
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    return 100;
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
    return "abc";
});
CompletableFuture<String> f =  future.thenCombine(future2, (x,y) -> y + "-" + x);
System.out.println(f.get()); //abc-100

3.5 辅助方法 allOf 和 anyOf

上面都是对两个操作进行组合,有时候也可以组合更多的操作

public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)

allOf方法是当所有的CompletableFuture都执行完后执行计算。

anyOf方法是当任意一个CompletableFuture执行完后就会执行计算,计算的结果相同。

他们接受数组类型的参数

CompletableFuture [] futures = {CompletableFuture.supplyAsync(()->randomDelay()),CompletableFuture.supplyAsync(()->randomDelay()),CompletableFuture.supplyAsync(()->randomDelay())};

CompletableFuture.allOf(futures).join();
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值