Java1.8 --CompletableFuture

CompletableFuture

本文转自https://juejin.im/post/5adbf8226fb9a07aac240a67

CompletableFuture 是 Future API的扩展。
Future 被用于作为一个异步计算结果的引用。提供一个 isDone() 方法来检查计算任务是否完成。当任务完成时,get() 方法用来接收计算任务的结果。
从 Callbale和 Future 教程可以学习更多关于 Future 知识.
Future API 是非常好的 Java 异步编程进阶,但是它缺乏一些非常重要和有用的特性。

Future 的局限性

不能手动完成
当你写了一个函数,用于通过一个远程API获取一个电子商务产品最新价格。因为这个 API 太耗时,你把它允许在一个独立的线程中,并且从你的函数中返回一个 Future。现在假设这个API服务宕机了,这时你想通过该产品的最新缓存价格手工完成这个Future 。你会发现无法这样做。
Future 的结果在非阻塞的情况下,不能执行更进一步的操作
Future 不会通知你它已经完成了,它提供了一个阻塞的 get() 方法通知你结果。你无法给 Future 植入一个回调函数,当 Future 结果可用的时候,用该回调函数自动的调用 Future 的结果。
多个 Future 不能串联在一起组成链式调用
有时候你需要执行一个长时间运行的计算任务,并且当计算任务完成的时候,你需要把它的计算结果发送给另外一个长时间运行的计算任务等等。你会发现你无法使用 Future 创建这样的一个工作流。
不能组合多个 Future 的结果
假设你有10个不同的Future,你想并行的运行,然后在它们运行未完成后运行一些函数。你会发现你也无法使用 Future 这样做。
没有异常处理
Future API 没有任务的异常处理结构居然有如此多的限制,幸好我们有CompletableFuture,你可以使用 CompletableFuture 达到以上所有目的。

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

  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()方法,它持有一个Runnable 对象,并返回 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()
复制代码你也可以以 lambda 表达式的形式传入 Runnable 对象:
// Using Lambda Expression
CompletableFuture future = CompletableFuture.runAsync(() -> {
// 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.”);
});
复制代码在本文中,我使用lambda表达式会比较频繁,如果以前你没有使用过,建议你也多使用lambda 表达式。
3. 使用 supplyAsync() 运行一个异步任务并且返回结果
当任务不需要返回任何东西的时候, CompletableFuture.runAsync() 非常有用。但是如果你的后台任务需要返回一些结果应该要怎么样?
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()方法,该方法可以写入你的后台任务中,并且返回结果。
你可以使用lambda表达式使得上面的示例更加简明:
// Using Lambda Expression
CompletableFuture future = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return “Result of the asynchronous computation”;
});
复制代码
一个关于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 转换和运行
CompletableFuture.get()方法是阻塞的。它会一直等到Future完成并且在完成后返回结果。
但是,这是我们想要的吗?对于构建异步系统,我们应该附上一个回调给CompletableFuture,当Future完成的时候,自动的获取结果。
如果我们不想等待结果返回,我们可以把需要等待Future完成执行的逻辑写入到回调函数中。
可以使用 thenApply(), thenAccept() 和thenRun()方法附上一个回调给CompletableFuture。

  1. thenApply()
    可以使用 thenApply() 处理和改变CompletableFuture的结果。持有一个Function<R,T>作为参数。Function<R,T>是一个简单的函数式接口,接受一个T类型的参数,产出一个R类型的结果。
    // Create a CompletableFuture
    CompletableFuture whatsYourNameFuture = CompletableFuture.supplyAsync(() -> {
    try {
    TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
    throw new IllegalStateException(e);
    }
    return “Rajeev”;
    });

// Attach a callback to the Future using thenApply()
CompletableFuture greetingFuture = whatsYourNameFuture.thenApply(name -> {
return "Hello " + name;
});

// Block and get the result of the future.
System.out.println(greetingFuture.get()); // 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()
如果你不想从你的回调函数中返回任何东西,仅仅想在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);
复制代码组合两个CompletableFuture

  1. 使用 thenCompose()组合两个独立的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<CompletableFuture> result = getUserDetail(userId)
.thenApply(user -> getCreditRating(user));
复制代码在更早的示例中,Supplier函数传入thenApply将返回一个简单的值,但是在本例中,将返回一个CompletableFuture。以上示例的最终结果是一个嵌套的CompletableFuture。
如果你想获取最终的结果给最顶层future,使用 thenCompose()方法代替-
CompletableFuture result = getUserDetail(userId)
.thenCompose(user -> getCreditRating(user));
复制代码因此,规则就是-如果你的回调函数返回一个CompletableFuture,但是你想从CompletableFuture链中获取一个直接合并后的结果,这时候你可以使用thenCompose()。
2. 使用thenCombine()组合两个独立的 future
虽然thenCompose()被用于当一个future依赖另外一个future的时候用来组合两个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
我们使用thenCompose()和 thenCombine()把两个CompletableFuture组合在一起。现在如果你想组合任意数量的CompletableFuture,应该怎么做?我们可以使用以下两个方法组合任意数量的CompletableFuture。
static CompletableFuture allOf(CompletableFuture<?>… cfs)
static CompletableFuture anyOf(CompletableFuture<?>… cfs)
复制代码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<CompletableFuture> 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。但是我们可以通过写一些额外的代码来获取所有封装的CompletableFuture结果。
// When all the Futures are completed, call future.join() to get their results and collect the results in a list -
CompletableFuture<List> 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 anyOfFuture = CompletableFuture.anyOf(future1, future2, future3);

System.out.println(anyOfFuture.get()); // Result of Future 2
复制代码在以上示例中,当三个中的任何一个CompletableFuture完成, anyOfFuture就会完成。因为future2的休眠时间最少,因此她最先完成,最终的结果将是future2的结果。
CompletableFuture.anyOf()传入一个Future可变参数,返回CompletableFuture。CompletableFuture.anyOf()的问题是如果你的CompletableFuture返回的结果是不同类型的,这时候你讲会不知道你最终CompletableFuture是什么类型。
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。

插入链接与图片

链接: link.

图片: Alt

带尺寸的图片: Alt

居中的图片: Alt

居中并且带尺寸的图片: Alt

当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。

如何插入一段漂亮的代码片

博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片.

// An highlighted block
var foo = 'bar';

生成一个适合你的列表

  • 项目
    • 项目
      • 项目
  1. 项目1
  2. 项目2
  3. 项目3
  • 计划任务
  • 完成任务

创建一个表格

一个简单的表格是这么创建的:

项目Value
电脑$1600
手机$12
导管$1

设定内容居中、居左、居右

使用:---------:居中
使用:----------居左
使用----------:居右

第一列第二列第三列
第一列文本居中第二列文本居右第三列文本居左

SmartyPants

SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如:

TYPEASCIIHTML
Single backticks'Isn't this fun?'‘Isn’t this fun?’
Quotes"Isn't this fun?"“Isn’t this fun?”
Dashes-- is en-dash, --- is em-dash– is en-dash, — is em-dash

创建一个自定义列表

Markdown
Text-to- HTML conversion tool
Authors
John
Luke

如何创建一个注脚

一个具有注脚的文本。1

注释也是必不可少的

Markdown将文本转换为 HTML

KaTeX数学公式

您可以使用渲染LaTeX数学表达式 KaTeX:

Gamma公式展示 Γ ( n ) = ( n − 1 ) ! ∀ n ∈ N \Gamma(n) = (n-1)!\quad\forall n\in\mathbb N Γ(n)=(n1)!nN 是通过欧拉积分

Γ ( z ) = ∫ 0 ∞ t z − 1 e − t d t   . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,. Γ(z)=0tz1etdt.

你可以找到更多关于的信息 LaTeX 数学表达式here.

新的甘特图功能,丰富你的文章

Mon 06 Mon 13 Mon 20 已完成 进行中 计划一 计划二 现有任务 Adding GANTT diagram functionality to mermaid
  • 关于 甘特图 语法,参考 这儿,

UML 图表

可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图:

张三 李四 王五 你好!李四, 最近怎么样? 你最近怎么样,王五? 我很好,谢谢! 我很好,谢谢! 李四想了很长时间, 文字太长了 不适合放在一行. 打量着王五... 很好... 王五, 你怎么样? 张三 李四 王五

这将产生一个流程图。:

链接
长方形
圆角长方形
菱形
  • 关于 Mermaid 语法,参考 这儿,

FLowchart流程图

我们依旧会支持flowchart的流程图:

Created with Raphaël 2.2.0 开始 我的操作 确认? 结束 yes no
  • 关于 Flowchart流程图 语法,参考 这儿.

导出与导入

导出

如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。

导入

如果你想加载一篇你写过的.md文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,
继续你的创作。


  1. 注脚的解释 ↩︎

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值