CompletableFuture组合式异步编程

从JDK8开始,在Concurrent包中提供了一个强大的异步编程工具CompletableFuture。在JDK8之前,异步编程可以通过线程池和Future来实现,但功能还不够强大。CompletableFuture的出现,使Java的异步编程能力向前迈进了一大步。

CompletableFuture,它是对并发编程的增强,它可以方便地将多个有一定依赖关系的异步任务以流水线的方式组合在一起,大大简化多异步任务的开发。

CompletableFuture,它是一个具体的类,实现了两个接口,一个是Future,另一个是CompletionStage。Future表示异步任务的结果,而CompletionStage的字面意思是完成阶段。多个CompletionStage可以以流水线的方式组合起来,对于其中一个CompletionStage,它有一个计算任务,但可能需要等待其他一个或多个阶段完成才能开始,它完成后,可能会触发其他阶段开始运行。CompletionStage提供了大量方法,使用它们,可以方便地响应任务事件,构建任务流水线,实现组合式异步编程。

 

最简单的用法

CompletableFuture实现了Future接口,所以它也具有Future的特 性:调用get()方法会阻塞在那,直到结果返回。

1
2
CompletableFuture<String> completableFuture = new CompletableFuture<String>();
String result = completableFuture.get();//调用者阻塞,等待结果返回

另外1个线程调用complete方法完成该Future,则所有阻塞在get ()方法的线程都将获得返回结果。

1
completableFuture.complete("this is a test result");

 

提交任务:runAsync与supplyAsync

上面的例子是一个空的任务,下面尝试提交一个真的任务,然后 等待结果返回。

例1:runAsync(Runnable)

1
2
3
4
5
6
7
8
9
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
    System.out.println("testtask is running");
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
});
future1.get();//主线程阻塞,等待任务执行完成

CompletableFuture.runAsync ( .. ) 传 入 的 是 一 个 Runnable 接 口,在上面的代码中是使用了Java 8的lambda表达式的写法,和定义 一个Runnable对象是等价的。

例2:supplyAsync(Supplier)

1
2
3
4
5
6
7
8
9
10
11
12
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(new Supplier<String>() {
    public String get() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "test result";
    }
});
String result = future2.get();//主线程阻塞,等待结果返回
System.out.println(result);//test result

例2和例1的区别在于,例2的任务有返回值。

通过上面两个例子可以看出,在基本的用法上,CompletableFuture和Future很相似,都可以提交两类任务:一类是无返回值的,另一 类是有返回值的。

 

链式的CompletableFuture:thenRun、thenAccept和 thenApply

对于 Future,在提交任务之后,只能调用 get()等结果返回; 但对于 CompletableFuture,可以在结果上面再加一个callback,当 得到结果之后,再接着执行callback。

例1:thenRun(Runnable)

1
2
3
4
5
6
7
8
9
10
11
12
CompletableFuture<Void> testFuture = CompletableFuture.supplyAsync(new Supplier<String>() {
    public String get() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "test result";
    }
}).thenRun(() -> {
    System.out.println("task A");
});

例2:thenAccept(Consumer)

1
2
3
4
5
6
7
8
9
10
11
12
CompletableFuture<Void> testFuture = CompletableFuture.supplyAsync(new Supplier<String>() {
    public String get() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "test result";
    }
}).thenAccept(result -> {
    System.out.println(result);
});

例3:thenApply(Function)

1
2
3
4
5
6
7
8
9
10
11
12
CompletableFuture<String> testFuture = CompletableFuture.supplyAsync(new Supplier<String>() {
    public String get() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "test result";
    }
}).thenApply(result -> {
    return result + "thenApply";
});

三个例子都是在任务执行完成之后,执行一个callback, 只是callback的形式有所区别:

(1)thenRun后面跟的是一个无参数、无返回值的方法,即Runnable,所以最终的返回值是CompletableFuture<;Void>;类型。

(2)thenAccept 后面跟的是一个有参数、无返回值的方法,称为 Consumer,返回值也是CompletableFuture<;Void>;类型。 顾名思义,只进不出,所以称为Consumer;前面的Supplier,是无参数,有返回值,只出不进,和Consumer刚好相反。

(3) thenApply 后面跟的是一个有参数、有返回值的方法,称为Function。返回值是CompletableFuture<;String>;类型。 而参数接收的是前一个任务,即 supplyAsync(..)这个任务的 返回值。因此这里只能用supplyAsync,不能用runAsync。因为runAsync没有返回值,不能为下一个链式方法传入参数。

 

CompletableFuture的组合:thenCompose与thenCombine

例1:thenCompose

在上面的例子中,thenApply接收的是一个Function,但是这个Fu nction的返回值是一个通常的基本数据类型或一个对象,而不是另外 一个 CompletableFuture。如果 Function 的返回值也是一个Complet ableFuture,就会出现嵌套的CompletableFuture。例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Test11 {
 
    static CompletableFuture<String> getUserId(String userId) {
        return CompletableFuture.supplyAsync(() -> {
            System.out.println("userId:" + userId);
            return userId;
        });
    }
 
    static CompletableFuture<String> getUserId2(String userId) {
        return CompletableFuture.supplyAsync(() -> {
            System.out.println("userId:" + userId);
            return userId;
        });
    }
 
    public static void main(String[] args) {
 
        // 这两个函数链式调用,代码如下所示
        CompletableFuture<CompletableFuture<String>> result = getUserId("111").thenApply(String -> getUserId2("112"));
        System.out.println(result.join().join());
 
        // 如果希望返回值是一个展平的CompletableFuture,可以使用thenCompose,代码如下所示
        CompletableFuture<String> result2 = getUserId("111").thenCompose(String -> getUserId2("112"));
        System.out.println(result2.join());
    }
}

下面是thenCompose函数的接口定义

1
2
3
public <U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn) {
    return uniComposeStage(null, fn);
}

从该函数的定义可以看出,它传入的参数是一个Function类型, 并且Function的返回值必须是CompletionStage的子类,也就是Comple tableFuture类型。

例2:thenCombine

thenCombine函数的接口定义如下,从传入的参数可以看出,它不 同于thenCompose

1
2
3
public <U,V> CompletableFuture<V> thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn) {
    return biApplyStage(null, other, fn);
}

第1个参数是一个CompletableFuture类型,第2个参数是一个函 数,并且是一个BiFunction,也就是该函数有2个输入参数,1个返回值。

从该接口的定义可以大致推测,它是要在2个CompletableFuture 完成之后,把2个CompletableFuture的返回值传进去,再额外做一些事情。实例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static void main(String[] args) throws ExecutionException, InterruptedException {
    // 函数1:获取体重
    CompletableFuture<Double> weightFuture = CompletableFuture.supplyAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 65.0;
    });
    // 函数2:获取身高
    CompletableFuture<Double> heightFuture = CompletableFuture.supplyAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 177.8;
    });
    // 函数3:结合身高、体重,计算BMI指数
    CompletableFuture<Double> bmiFuture = weightFuture.thenCombine(heightFuture, (weight, height) -> {
        Double heightInMeter = height / 100;
        return weight / (heightInMeter * heightInMeter);
    });
    System.out.println("Your BMI is - " + bmiFuture.get());
}

 

任意个CompletableFuture的组合

上面的thenCompose和thenCombine只能组合2个CompletableFutur e,而接下来的allOf 和anyOf 可以组合任意多个CompletableFutur e。函数接口定义如下所示。

1
2
3
4
5
6
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) {
    return andTree(cfs, 0, cfs.length - 1);
}
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs) {
    return orTree(cfs, 0, cfs.length - 1);
}

首先,这两个函数都是静态函数,参数是变长的CompletableFutu re的集合。其次,allOf和anyOf的区别,前者是“与”,后者是 “或”。

例1:allOf

allOf的返回值是CompletableFuture<;Void>;类型,这是 因为每个传入的CompletableFuture的返回值都可能不同,所以组合的 结果是无法用某种类型来表示的,索性返回Void类型。

例2:anyOf

anyOf 的含义是只要有任意一个 CompletableFuture 结束,就可 以做接下来的事情,而无须像AllOf那样,等待所有的CompletableFut ure结束。 但由于每个CompletableFuture的返回值类型都可能不同,任意一 个,意味着无法判断是什么类型,所以anyOf的返回值是CompletableFuture<;Object>;类型。

 

四种任务原型

通过上面的例子可以总结出,提交给CompletableFuture执行的任 务有四种类型:Runnable、Consumer、Supplier、Function。表格8-1 总结了这四种任务原型的对比。

runAsync 与 supplierAsync 是 CompletableFutre 的静态方法;而 thenAccept、thenAsync、thenApply是CompletableFutre的成 员方法。 因为初始的时候没有CompletableFuture对象,也没有参数可传, 所以提交的只能是Runnable或者Supplier,只能是静态方法; 通过静态方法生成CompletableFuture对象之后,便可以链式地提 交其他任务了,这个时候就可以提交Runnable、Consumer、Function,且都是成员方法。

 

参考: Java并发实现原理:JDK源码剖析 第8章 CompletableFuture

 

posted @   草木物语  阅读(569)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
历史上的今天:
2019-01-28 springboot 拦截器
2018-01-28 mysql update语句
点击右上角即可分享
微信分享提示