CompleteableFuture异步调用

在Java中CompletableFuture用于异步编程, 是 Future API的扩展。异步编程是编写非阻塞的代码,运行的任务在一个单独的线程,与主线程隔离,并且会通知主线程它的进度,成功或者失败。

Future

Future 被用于作为一个异步计算结果的引用。提供一个 isDone() 方法来检查计算任务是否完成。当任务完成时,get() 方法用来接收计算任务的结果。

Future 的局限性

  • 没有回调函数。Future提供了一个阻塞的 get() 方法通知你结果,在非阻塞的情况下,不能执行进一步操作 ,Future 不会通知你它已经完成;你无法给 Future 植入一个回调函数,当 Future 结果可用的时候,用该回调函数自动的调用 Future 的结果。

  • 无法链式调用。多个 Future 不能串联在一起组成链式调用

  • 不能组合多个Future。假设你有10个不同的Future,你想并行的运行,然后在它们运行未完成后运行一些函数。你会发现你也无法使用 Future 这样做。

  • 没有异常处理。

  • 不能手动结束。当你写了一个Future,用于通过一个远程API获取一个电子商务产品最新价格。因为这个 API 太耗时,你把它允许在一个独立的线程中,并且从你的函数中返回一个 Future。现在假设这个API服务宕机了,这时你想通过该产品的最新缓存价格手工完成这个Future 。你会发现无法这样做。

CompletableFuture 实现了 FutureCompletionStage接口,并且提供了许多关于创建,链式调用和组合多个 Future 的便利方法集,而且有广泛的异常处理支持。

CompletableFuture的API

1. get、join——阻塞等待返回结果

get() 方法:阻塞等待任务结果

join()方法:和get()方法非常类似,会一直阻塞直到 Future 完成。这唯一不同的地方是如果最顶层的CompletableFuture完成的时候发生了异常,它会抛出一个未经检查的异常。

2. runAsync

runAsync: 执行异步任务,任务没有返回结果。

接口:CompletableFuture<Void> runAsync(Runnable runnable)

示例

CompletableFuture<Void> runAsync = CompletableFuture.runAsync(() -> {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        log.info("ending...");
    });

runAsync.get();

3. supplyAsync

supplyAsync: 执行异步任务,任务有返回结果。

接口定义:

  • CompletableFuture<U> supplyAsync(Supplier<U> supplier): 函数式接口编程
  • CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor): 线程池执行
  • CompletableFuture<Void> runAsync(Runnable runnable)

示例

CompletableFuture<Integer> result = CompletableFuture.supplyAsync(() -> {
            int sum = 0;
            for (int i = 0; i < Integer.MAX_VALUE - 1; i++) {
                sum++;
            }
            return sum;
        });

log.info("计算结果:{}", result.join());

4. thenApply(), thenAccept()thenRun()

thenApply(), thenAccept()thenRun()方法:附一个回调给CompletableFuture。

回调函数:我们使用CompletableFuture.get()获取结果时是阻塞等待的,它会一直等到Future完成并且在完成后返回结果。当我们不想等待结果返回,我们可以把需要等待Future完成执行的逻辑写入到回调函数中。

4.1 thenApply

thenApply: 接受上一个回调结果, 继续异步任务,并返回异步执行的结果。

接口定义:CompletableFuture<U> thenApply(Function<? super T,? extends U> fn): Function函数式接口,接受一个T类型的参数,产出一个U类型的结果。

示例

CompletableFuture<Long> future = CompletableFuture.supplyAsync(() -> {
            long sum = 0L;
            for (int i = 0; i < 1000_000_000; i++) {
                sum++;
            }
            log.info("step 1 ending...");
            return sum;
        }).thenApply((sum) -> {
            Sleeper.sleep(1);
            return sum;
        });

log.info("计算结果:{}", future.join());

注:在thenApply()中的异步任务和上一步中的任务执行在相同的线程中

4.2 thenAccept

thenAccept: 接受上一个回调结果, 继续异步任务,不返回结果

接口定义:CompletableFuture<Void> thenAccept(Consumer<? super T> action)

示例:

CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return 100;
        }).thenAccept((result) -> {
            log.info("the result of previous step{}", result);

        }).join();

log.info("main....");

4.3 thenRun

thenRun: 不接受上个任务的回调,继续异步任务,不返回结果

接口定义:CompletableFuture<Void> thenRun(Runnable action)

CompletableFuture.supplyAsync(() -> {
            Sleeper.sleep(1);
            return 100;
}).thenRun(() -> {
            log.info("end...");
}).join();

5. thenCompose和thenCombine

5.1 thenCompose

thenCompose:被用于当一个future依赖另外一个future的时候用来组合两个future,执行异步任务,返回结果。

接口定义:

  • CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn)

  • CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) : 异步执行

thenCompose看起来和thenApply类似,thenCompose和thenApply的区别是?

考虑下以下两个方法getUserDetail()getCreditRating()的实现, thenApply是否能达到我们期望的结果。

CompletableFuture<User> getUsersDetail(String userId) {
	return CompletableFuture.supplyAsync(() -> {
		UserService.getUserDetails(userId);
	});	
}

CompletableFuture<Double> getCreditRating(User user) {
	return CompletableFuture.supplyAsync(() -> {
		CreditRatingService.getCreditRating(user);
	});
}

CompletableFuture<CompletableFuture<Double>> result = getUserDetail(userId)
.thenApply(user -> getCreditRating(user));

我们预期获得的是CompletableFuture, 但是使用thenApply获得的结果为CompletableFuture<CompletableFuture>。 这是因为thenApply(Supplier<T,R>()): Supplier函数传入thenApply将返回一个简单的值,但是在本例中,将 getUsersDetail返回给thenApply一个CompletableFuture。

Supplier函数传入thenApply将返回一个简单的值,如果在上一步中最终结果是一个嵌套的CompletableFuture。 如果你想获取最终的结果给顶层future,使用 thenCompose()方法代替。

CompletableFuture<Double> result = getUserDetail(userId)
.thenCompose(user -> getCreditRating(user));

5.2 thenCombine

thenCombine(): 被用来当两个独立的Future都完成的时候,用来做一些事情。

接口定义:<U,V> CompletableFuture<V> thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn)

示例:

public static void main(String[] args) {
        CompletableFuture<Double> future = wightKgFuture().thenCombine(heightCmFuture(), (w, h) -> {
            Double heightMeter = h / 100;

            return w / (heightMeter * heightMeter);
        });

        log.info("Your BMI is - {}", future.join());
    }

    public static CompletableFuture<Double> wightKgFuture() {
        return CompletableFuture.supplyAsync(() ->{
            Sleeper.sleep(1);

            return 53.0;
        });
    }

    public static CompletableFuture<Double> heightCmFuture() {
        return CompletableFuture.supplyAsync(() ->{
            Sleeper.sleep(1.5);

            return 169.5;
        });
    }

6. allOf

allOf: 等待所有异步任务都结束之后,做一些事情

接口:CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)

假设你想在一个网站的100个不同的页面上冲浪。你可以串行的做这个操作,但是这非常消耗时间。因此你想写一个函数,传入一个页面链接,返回一个CompletableFuture,异步的冲浪页面内容。

public static void main(String[] args) {

        String[] urls = {"baidu.com", "google.com", "bing.com", "bingo.com"};
        List<String> urlist = Arrays.asList(urls);

        List<CompletableFuture<String>> futureList = urlist.stream()
                .map(AllOfTest_9::surfing)
                .collect(Collectors.toList());

        //把List<CompletableFuture<String>> -> List<String>
        List<String> result = CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0]))
                .thenApply(v -> futureList.stream().map(CompletableFuture::join).collect(Collectors.toList()))
                .join();

        log.info("{}", result);
    }

    private static CompletableFuture<String> surfing(String url) {
        return CompletableFuture.supplyAsync(() -> {
            Sleeper.sleep(1);
            return "www." + url;
        });
    }

7. anyOf

anyOf: 任意一个异步任务都结束之后,做一些事情

接口:CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)

假如Jackson、Harry和David在赛跑,获取谁是冠军。

public static void main(String[] args) {
        Object join = CompletableFuture.anyOf(Jackson(), Hayley(), David()).join();
        log.info("{}", join);
    }

    public static CompletableFuture<String> Jackson() {
        return CompletableFuture.supplyAsync(() ->{
            Sleeper.sleep(2);

            return "Jackson win";
        });
    }

    public static CompletableFuture<String> Hayley() {
        return CompletableFuture.supplyAsync(() ->{
            Sleeper.sleep(1);

            return "Harry win";
        });
    }

    public static CompletableFuture<String> David() {
        return CompletableFuture.supplyAsync(() ->{
            Sleeper.sleep(2);

            return "David win";
        });
    }

8. CompletableFuture异常处理

首先让我们明白在一个回调链中错误是怎么传递的。思考下以下回调链:

CompletableFuture.supplyAsync(() -> {
	// Code which might throw an exception
	return "Some result";
}).thenApply(result -> {
	return "processed result";
}).thenApply(result -> {
	return "result after further processing";
}).thenAccept(result -> {
	// do something with the final result
});

如果在原始的supplyAsync()任务中发生一个错误,这时候没有任何thenApply会被调用并且future将以一个异常结束。如果在第一个thenApply发生错误,这时候第二个和第三个将不会被调用,同样的,future将以异常结束。

8.1 exceptionally

exceptionally()回调处理异常:提供一个回调函数,给你一个从原始Future中生成的错误恢复的机会。你可以在这里记录这个异常并返回一个默认值

public static void main(String[] args) {
        int age = 0;
        CompletableFuture<String> result = CompletableFuture.supplyAsync(() -> {
            Sleeper.sleep(1);

            if (age == 0) {
                throw new IllegalArgumentException("Age can not be negative");
            }

            return "OK";
        }).exceptionally(ex -> {
            log.warn("Oops! We have an exception - {}", ex.getMessage());
            return "No";
        });

        log.info("{}", result.join());
}

8.2 handle

Handle: 处理异常, 无论一个异常是否发生它都会被调用, 处理异常,返回一个默认值。

public static void main(String[] args) {
        int a = 0;

        CompletableFuture<String> result = CompletableFuture.supplyAsync(() -> {
            Sleeper.sleep(1);

            if (a == 0) {
                throw new RuntimeException("Age can not be negative");
            }

            return "OK";
        }).handle((res, ex) -> {
            if (ex != null) {
                log.warn("Oops! We have an exception - {}", ex.getMessage());
                res = "NO";
            }

            return res;
        });

        log.info("{}", result.join());
}
posted @ 2022-08-24 16:58  言思宁  阅读(1378)  评论(0编辑  收藏  举报