CompletableFuture 异步编程

我们知道Java是一个支持多线程的语言,多线程模式的并发编程模型在Java中一直是主流,然而随着时代发展我们也认识到,其实异步编程模型在某些场景下更为适合,Java当然也可以实现异步编程,但始终缺乏一些语言或是库层面的支持,大量的异步操作容易出现「回调地狱」等情况。

直到JDK8中引入了一个CompletableFuture对象,它和JavaScript中的Promise有些类似,通过这个对象我们能较为简单的编写Java异步代码。

supplyAsync 创建异步任务

我们可以使用静态方法CompletableFuture.runAsync()CompletableFuture.supplyAsync()创建异步任务。这里我们直接看一个例子。

package com.gacfox.demo;

import java.util.concurrent.*;

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建异步任务
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("异步任务执行完成");
        });
        // 主线程阻塞等待
        future.get();
    }
}

代码中,我们通过CompletableFuture.runAsync()创建了一个异步任务,然后调用future.get()让主线程阻塞等待异步任务返回。

CompletableFuture也能够获取返回值,我们使用supplyAsync()方法创建异步任务即可,下面是一个例子。

package com.gacfox.demo;

import java.util.concurrent.*;

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建异步任务
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("异步任务执行完成");
            return "success";
        });
        // 主线程阻塞等待
        String s = future.get();
        System.out.println("返回值:" + s);
    }
}

thenApply 异步任务依赖

有时多个异步任务有依赖关系,比如我们先调用接口A获取数据,再根据A的返回值调用接口B获取数据,这两个异步任务就是有依赖的,需要按照先后顺序串行执行。如果不使用CompletableFuture我们就得写一个嵌套的异步调用,嵌套层数太多代码可读性就很低了。而CompletableFuture能够很好的解决这个问题,下面是一个例子。

package com.gacfox.demo;

import java.util.concurrent.*;

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建异步任务
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("调用接口A");
            return "aaa";
        }).thenApply((s) -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("调用接口B");
            return s + "bbb";
        });
        // 主线程阻塞等待
        String s = future.get();
        System.out.println("返回值:" + s);
    }
}

上面代码中,会顺序执行两个异步任务,最终输出aaabbb

除了thenApply()方法还有一个thenRun()方法,区别是没有上一步的传参,这里就不展开介绍了。

thenAccept 消费最终结果

前面代码我们都是在主线程中进行阻塞等待,其实这不太符合异步任务的实际使用场景,实际开发中,更多的时候我们需要异步任务完成后自己进行一些处理,而不需要主线程等着它完成。此时我们可以调用thenAccept方法实现。

package com.gacfox.demo;

import java.util.concurrent.*;

public class Main {
    public static void main(String[] args) {
        // 创建异步任务
        CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("执行异步任务");
            return "aaa";
        }).thenAccept((s) -> {
            System.out.println("消费最终结果");
            System.out.println("输出" + s);
        });
    }
}

上面代码中,我们的异步任务执行完后,会自动调用thenAccept()中的内容,主线程不会被阻塞了。

注:实际运行上述代码,其实你是看不到任何返回值的,因为还没等异步任务执行完主线程就结束了,这也侧面说明了主线程没有被阻塞。要想看到返回结果,我们借助让主线程睡眠等手段来实现,这里就不赘述了。

exceptionally 异常处理

我们可以调用exceptionally进行异常处理,下面是一个例子。

package com.gacfox.demo;

import java.util.concurrent.*;

public class Main {
    public static void main(String[] args) {
        // 创建异步任务
        CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            int i = 1 / 0;
            return "aaa";
        }).exceptionally((ex) -> {
            System.out.println("发生了异常:" + ex.getMessage());
            return "bbb";
        });
    }
}

这里由于我们手动触发了1/0的除零异常,因此整个执行流程继续交给了exceptionally()进行处理。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。