get()
方法通知你结果。你无法给 Future 植入一个回调函数,当 Future 结果可用的时候,用该回调函数自动的调用 Future 的结果。CompletableFuture 实现了 Future
和 CompletionStage
接口,并且提供了许多关于创建,链式调用和组合多个 Future 的便利方法集,而且有广泛的异常处理支持,解决了以上Futrue的所有缺陷。
1. 简单的例子
可以使用如下无参构造函数简单的创建 CompletableFuture:
CompletableFuture completableFuture = new CompletableFuture();
这是一个最简单的 CompletableFuture,想获取CompletableFuture 的结果可以使用 CompletableFuture.get()
方法:
String result = completableFuture.get()
get()
方法会一直阻塞直到 Future 完成。因此,以上的调用将被永远阻塞,因为该Future一直不会完成。
你可以使用 CompletableFuture.complete()
手工的完成一个 Future:
completableFuture.complete("Future's Result")
所有等待这个 Future 的客户端都将得到一个指定的结果,并且 completableFuture.complete()
之后的调用将被忽略。
2. 使用 runAsync()
运行异步任务没有返回结果
如果你想异步的运行一个后台任务并且不想返回任务结果,这时候可以使用 CompletableFuture.runAsync()
方法,它持有一个Runbable对象,并返回 CompletableFuture
。
// Run a task specified by a Runnable Object asynchronously.
CompletableFuture future = CompletableFuture.runAsync(new Runnable() {
@Override
public void run() {
// Simulate a long-running Job
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
System.out.println("I'll run in a separate thread than the main thread.");
}
});
// Block and wait for the future to complete
future.get()
3. 使用 supplyAsync()
运行一个异步任务并且返回结果
CompletableFuture.supplyAsync()可以返回结果,它持有supplier
并且返回CompletableFuture
,T
是通过调用 传入的supplier取得的值的类型。
// Run a task specified by a Supplier object asynchronously
CompletableFuture future = CompletableFuture.supplyAsync(new Supplier() {
@Override
public String get() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return "Result of the asynchronous computation";
}
});
// Block and get the result of the Future
String result = future.get();
System.out.println(result);
Supplier
是一个简单的函数式接口,表示supplier的结果。它有一个get()
方法,该方法可以写入你的后台任务中,并且返回结果。
一个关于Executor 和Thread Pool笔记
你可能想知道,我们知道runAsync()
和supplyAsync()
方法在单独的线程中执行他们的任务。但是我们不会永远只创建一个线程。
CompletableFuture可以从全局的 ForkJoinPool.commonPool()获得一个线程中执行这些任务。
但是你也可以创建一个线程池并传给runAsync()
和supplyAsync()
方法来让他们从线程池中获取一个线程执行它们的任务。
CompletableFuture API 的所有方法都有两个变体-一个接受Executor
作为参数,另一个不这样:
// Variations of runAsync() and supplyAsync() methods
static CompletableFuture runAsync(Runnable runnable)
static CompletableFuture runAsync(Runnable runnable, Executor executor)
static CompletableFuture supplyAsync(Supplier supplier)
static CompletableFuture supplyAsync(Supplier supplier, Executor executor)
创建一个线程池,并传递给其中一个方法:
Executor executor = Executors.newFixedThreadPool(10);
CompletableFuture future = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return "Result of the asynchronous computation";
}, executor);
CompletableFuture.get()
方法是阻塞的。它会一直等到Future完成并且在完成后返回结果。
但是,这是我们想要的吗?对于构建异步系统,我们应该附上一个回调给CompletableFuture,当Future完成的时候,自动的获取结果。
如果我们不想等待结果返回,我们可以把需要等待Future完成执行的逻辑写入到回调函数中。
可以使用 thenApply()
, thenAccept()
和thenRun()
方法附上一个回调给CompletableFuture。
1. thenApply()
可以使用 thenApply()
处理和改变CompletableFuture的结果。持有一个Function
作为参数。Function
是一个简单的函数式接口,接受一个T类型的参数,产出一个R类型的结果。
CompletableFuture whatsYourNameFuture = CompletableFuture.supplyAsync(new Supplier() {
@Override
public String get() {
// TODO Auto-generated method stub
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "Rajeev";
}
});
CompletableFuture greetingFutrue = whatsYourNameFuture.thenApply(new Function() {
@Override
public String apply(String t) {
// TODO Auto-generated method stub
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "Hello," + t;
}
});// Hello Rajeev
你也可以通过附加一系列的thenApply()
在回调方法 在CompletableFuture写一个连续的转换。这样的话,结果中的一个 thenApply
方法就会传递给该系列的另外一个 thenApply
方法。
CompletableFuture welcomeText = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return "Rajeev";
}).thenApply(name -> {
return "Hello " + name;
}).thenApply(greeting -> {
return greeting + ", Welcome to the CalliCoder Blog";
});
System.out.println(welcomeText.get());
// Prints - Hello Rajeev, Welcome to the CalliCoder Blog
2. thenAccept() 和 thenRun()
thenAccept接收上一阶段的输出作为本阶段的输入
如果你不想从你的回调函数中返回任何东西,仅仅想在Future完成后运行一些代码片段,你可以使用thenAccept()
和 thenRun()
方法,这些方法经常在调用链的最末端的最后一个回调函数中使用。CompletableFuture.thenAccept()
持有一个Consumer
,返回一个CompletableFuture
。它可以访问CompletableFuture
的结果:
// thenAccept() example
CompletableFuture.supplyAsync(() -> {
return ProductService.getProductDetail(productId);
}).thenAccept(product -> {
System.out.println("Got product detail from remote service " + product.getName())
});
虽然thenAccept()
可以访问CompletableFuture的结果,但thenRun()
不能访问Future的结果,它持有一个Runnable返回CompletableFuture
// thenRun() example
CompletableFuture.supplyAsync(() -> {
// Run some computation
}).thenRun(() -> {
// Computation Finished.
});
异步回调方法的笔记
CompletableFuture提供的所有回调方法都有两个变体:
`// thenApply() variants
CompletableFuture thenApply(Function super T,? extends U> fn)
CompletableFuture thenApplyAsync(Function super T,? extends U> fn)
CompletableFuture thenApplyAsync(Function super T,? extends U> fn, Executor executor)`
这些异步回调变体通过在独立的线程中执行回调任务帮助你进一步执行并行计算。
以下示例:
CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return "Some Result"
}).thenApply(result -> {
/*
Executed in the same thread where the supplyAsync() task is executed
or in the main thread If the supplyAsync() task completes immediately (Remove sleep() call to verify)
*/
return "Processed Result"
})
在以上示例中,在thenApply()
中的任务和在supplyAsync()
中的任务执行在相同的线程中。任何supplyAsync()
立即执行完成,那就是执行在主线程中(尝试删除sleep测试下)。
为了控制执行回调任务的线程,你可以使用异步回调。如果你使用thenApplyAsync()
回调,将从ForkJoinPool.commonPool()
获取不同的线程执行。
CompletableFuture.supplyAsync(() -> {
return "Some Result"
}).thenApplyAsync(result -> {
// Executed in a different thread from ForkJoinPool.commonPool()
return "Processed Result"
})
此外,如果你传入一个Executor
到thenApplyAsync()
回调中,,任务将从Executor线程池获取一个线程执行。
Executor executor = Executors.newFixedThreadPool(2);
CompletableFuture.supplyAsync(() -> {
return "Some result"
}).thenApplyAsync(result -> {
// Executed in a thread obtained from the executor
return "Processed Result"
}, executor);
1. 使用 thenCompose()
组合两个独立的future
thenCompose()
被用于当一个future依赖另外一个future的时候用来组合两个future。
假设你想从一个远程API中获取一个用户的详细信息,一旦用户信息可用,则通过获取到的用户详细信息从另外一个服务中获取他的贷方。
考虑下以下两个方法getUserDetail()
和getCreditRating()
的实现:
CompletableFuture getUsersDetail(String userId) {
return CompletableFuture.supplyAsync(() -> {
UserService.getUserDetails(userId);
});
}
CompletableFuture getCreditRating(User user) {
return CompletableFuture.supplyAsync(() -> {
CreditRatingService.getCreditRating(user);
});
}
当使用了thenApply()
后是否会达到我们期望的结果
CompletableFuture> result = getUserDetail(userId)
.thenApply(user -> getCreditRating(user));
在更早的示例中,Supplier
函数传入thenApply
将返回一个简单的值,但是在本例中,将返回一个CompletableFuture。以上示例的最终结果是一个嵌套的CompletableFuture。
如果你想获取最终的结果给最顶层future,使用 thenCompose()
方法代替-
CompletableFuture result = getUserDetail(userId)
.thenCompose(user -> getCreditRating(user));
2. 使用thenCombine()
组合两个独立的 future
thenCombine()
被用来当两个独立的Future
都完成的时候,用来做一些事情。
System.out.println("Retrieving weight.");
CompletableFuture weightInKgFuture = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return 65.0;
});
System.out.println("Retrieving height.");
CompletableFuture heightInCmFuture = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return 177.8;
});
System.out.println("Calculating BMI.");
CompletableFuture combinedFuture = weightInKgFuture
.thenCombine(heightInCmFuture, (weightInKg, heightInCm) -> {
Double heightInMeter = heightInCm/100;
return weightInKg/(heightInMeter*heightInMeter);
});
System.out.println("Your BMI is - " + combinedFuture.get());
当两个Future都完成的时候,传给``thenCombine()的回调函数将被调用。
如果你想组合任意数量的CompletableFuture,应该怎么做?我们可以使用以下两个方法组合任意数量的CompletableFuture。
static CompletableFuture allOf(CompletableFuture>... cfs)
static CompletableFuture
1. CompletableFuture.allOf()CompletableFuture.allOf
的使用场景是当你一个列表的独立future,并且你想在它们都完成后并行的做一些事情。
假设你想下载一个网站的100个不同的页面。你可以串行的做这个操作,但是这非常消耗时间。因此你想写一个函数,传入一个页面链接,返回一个CompletableFuture,异步的下载页面内容。
CompletableFuture downloadWebPage(String pageLink) {
return CompletableFuture.supplyAsync(() -> {
// Code to download and return the web page's content
});
}
现在,当所有的页面已经下载完毕,你想计算包含关键字CompletableFuture
页面的数量。可以使用CompletableFuture.allOf()
达成目的。
List webPageLinks = Arrays.asList(...) // A list of 100 web page links
// Download contents of all the web pages asynchronously
List> pageContentFutures = webPageLinks.stream()
.map(webPageLink -> downloadWebPage(webPageLink))
.collect(Collectors.toList());
// Create a combined Future using allOf()
CompletableFuture allFutures = CompletableFuture.allOf(
pageContentFutures.toArray(new CompletableFuture[pageContentFutures.size()])
);
使用CompletableFuture.allOf()
的问题是它返回CompletableFuture
// When all the Futures are completed, call `future.join()` to get their results and collect the results in a list -
CompletableFuture> allPageContentsFuture = allFutures.thenApply(v -> {
return pageContentFutures.stream()
.map(pageContentFuture -> pageContentFuture.join())
.collect(Collectors.toList());
});
花一些时间理解下以上代码片段。当所有future完成的时候,我们调用了future.join()
,因此我们不会在任何地方阻塞。
join()
方法和get()
方法非常类似,这唯一不同的地方是如果最顶层的CompletableFuture完成的时候发生了异常,它会抛出一个未经检查的异常。
现在让我们计算包含关键字页面的数量。
// Count the number of web pages having the "CompletableFuture" keyword.
CompletableFuture countFuture = allPageContentsFuture.thenApply(pageContents -> {
return pageContents.stream()
.filter(pageContent -> pageContent.contains("CompletableFuture"))
.count();
});
System.out.println("Number of Web Pages having CompletableFuture keyword - " +
countFuture.get());
2. CompletableFuture.anyOf()
CompletableFuture.anyOf()
和其名字介绍的一样,当任何一个CompletableFuture完成的时候【相同的结果类型】,返回一个新的CompletableFuture。以下示例:
CompletableFuture future1 = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return "Result of Future 1";
});
CompletableFuture future2 = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return "Result of Future 2";
});
CompletableFuture future3 = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return "Result of Future 3";
});
CompletableFuture
在以上示例中,当三个中的任何一个CompletableFuture完成, anyOfFuture
就会完成。因为future2
的休眠时间最少,因此她最先完成,最终的结果将是future2
的结果。
CompletableFuture.anyOf()
传入一个Future可变参数,返回CompletableFuture
我们探寻了怎样创建CompletableFuture,转换它们,并组合多个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将以异常结束。
1. 使用 exceptionally() 回调处理异常exceptionally()
回调给你一个从原始Future中生成的错误恢复的机会。你可以在这里记录这个异常并返回一个默认值。
Integer age = -1;
CompletableFuture maturityFuture = CompletableFuture.supplyAsync(() -> {
if(age < 0) {
throw new IllegalArgumentException("Age can not be negative");
}
if(age > 18) {
return "Adult";
} else {
return "Child";
}
}).exceptionally(ex -> {
System.out.println("Oops! We have an exception - " + ex.getMessage());
return "Unknown!";
});
System.out.println("Maturity : " + maturityFuture.get());
2. 使用 handle() 方法处理异常
API提供了一个更通用的方法 - handle()
从异常恢复,无论一个异常是否发生它都会被调用。
Integer age = -1;
CompletableFuture maturityFuture = CompletableFuture.supplyAsync(() -> {
if(age < 0) {
throw new IllegalArgumentException("Age can not be negative");
}
if(age > 18) {
return "Adult";
} else {
return "Child";
}
}).handle((res, ex) -> {
if(ex != null) {
System.out.println("Oops! We have an exception - " + ex.getMessage());
return "Unknown!";
}
return res;
});
System.out.println("Maturity : " + maturityFuture.get());
如果异常发生,res
参数将是 null,否则,ex
将是 null。