CompletableFuture1

This commit is contained in:
zhangzw
2019-08-11 16:18:58 +08:00
parent d1d8bf65ed
commit 0c08356b99

View File

@@ -1272,11 +1272,169 @@ public class QuittingTasks {
<!-- CompletableFutures -->
## CompletableFuture类
作为介绍这里是使用CompletableFutures在QuittingTasks.java中
```java
// concurrent/QuittingCompletable.java
import java.util.*;
import java.util.stream.*;
import java.util.concurrent.*;
import onjava.Nap;
public class QuittingCompletable {
public static void main(String[] args) {
List<QuittableTask> tasks =
IntStream.range(1, QuittingTasks.COUNT)
.mapToObj(QuittableTask::new)
.collect(Collectors.toList());
List<CompletableFuture<Void>> cfutures =
tasks.stream()
.map(CompletableFuture::runAsync)
.collect(Collectors.toList());
new Nap(1);
tasks.forEach(QuittableTask::quit);
cfutures.forEach(CompletableFuture::join);
}
}
/* Output:7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 2526 27 28 29 30 31 32 33 34 6 35 4 38 39 40 41 42 43 4445 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 6263 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 8081 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 9899 100 101 102 103 104 105 106 107 108 109 110 111 1121 113 114 116 117 118 119 120 121 122 123 124 125 126127 128 129 130 131 132 133 134 135 136 137 138 139 140141 142 143 144 145 146 147 148 149 5 115 37 36 2 3*/
```
任务是一个**List <QuittableTask>**,就像在**QuittingTasks.java**中一样,但是在这个例子中,没有**peek()**将每个**QuittableTask**提交给**ExecutorService**。相反在创建cfutures期间每个任务都交给**CompletableFuture::runAsync**。这执行**VerifyTask.run(**)并返回**CompletableFuture <Void>**。因为**run()**不返回任何内容,所以在这种情况下我只使用**CompletableFuture**调用**join()**来等待它完成。
在此示例中需要注意的重要事项是,运行任务不需要**ExecutorService**。这由**CompletableFuture**管理(尽管有提供自己的**ExecutorService**的选项)。你也不需要调用**shutdown()**;事实上,除非你像我这样明确地调用**join()**,程序将尽快退出,而不必等待任务完成。
这个例子只是一个起点。你很快就会看到ComplempleFutures能够做得更多。
### 基本用法
这是一个带有静态方法**work()**的类,它对该类的对象执行某些工作:
```java
// concurrent/Machina.java
import onjava.Nap;
public class Machina {
public enum State {
START, ONE, TWO, THREE, END;
State step() {
if(equals(END))
return END;
return values()[ordinal() + 1];
}
}
private State state = State.START;
private final int id;
public Machina(int id) {
this.id = id;
}
public static Machina work(Machina m) {
if(!m.state.equals(State.END)){
new Nap(0.1);
m.state = m.state.step();
}
System.out.println(m);return m;
}
@Override
public StringtoString() {
return"Machina" + id + ": " + (state.equals(State.END)? "complete" : state);
}
}
```
这是一个有限状态机,一个微不足道的机器,因为它没有分支......它只是从头到尾遍历一条路径。**work()**方法将机器从一个状态移动到下一个状态并且需要100毫秒才能完成“工作”。
我们可以用**CompletableFuture**做的一件事是使用**completedFuture()**将它包装在感兴趣的对象中
```java
// concurrent/CompletedMachina.java
import java.util.concurrent.*;
public class CompletedMachina {
public static void main(String[] args) {
CompletableFuture<Machina> cf =
CompletableFuture.completedFuture(
new Machina(0));
try {
Machina m = cf.get(); // Doesn't block
} catch(InterruptedException |
ExecutionException e) {
throw new RuntimeException(e);
}
}
}
```
**completedFuture()**创建一个“已经完成”的**CompletableFuture**。对这样一个未来做的唯一有用的事情是**get()**里面的对象,所以这看起来似乎没有用。注意**CompletableFuture**被输入到它包含的对象。这个很重要。
通常,**get()**在等待结果时阻塞调用线程。此块可以通过**InterruptedException**或**ExecutionException**中断。在这种情况下阻止永远不会发生因为CompletableFutureis已经完成所以答案立即可用。
当我们将Machina包装在CompletableFuture中时我们发现我们可以在CompletableFuture上添加操作来处理所包含的对象事情变得更加有趣
```java
// concurrent/CompletableApply.java
import java.util.concurrent.*;
public class CompletableApply {
public static void main(String[] args) {
CompletableFuture<Machina> cf =
CompletableFuture.completedFuture(
new Machina(0));
CompletableFuture<Machina> cf2 =
cf.thenApply(Machina::work);
CompletableFuture<Machina> cf3 =
cf2.thenApply(Machina::work);
CompletableFuture<Machina> cf4 =
cf3.thenApply(Machina::work);
CompletableFuture<Machina> cf5 =
cf4.thenApply(Machina::work);
}
}
/* Output:
Machina0: ONE
Machina0: TWO
Machina0: THREE
Machina0: complete
*/
```
**thenApply()**应用一个接受输入并产生输出的函数。在这种情况下,**work()**函数产生与它相同的类型,因此每个得到的**CompletableFuture**仍然被输入为**Machina**,但是(类似于**Streams**中的**map()****Function**也可以返回不同的类型,这将反映在返回类型
您可以在此处看到有关CompletableFutures的重要信息它们会在您执行操作时自动解包并重新包装它们所携带的对象。这样你就不会陷入麻烦的细节这使得编写和理解代码变得更加简单。
我们可以消除中间变量并将操作链接在一起就像我们使用Streams一样
```java
// concurrent/CompletableApplyChained.javaimport java.util.concurrent.*;
import onjava.Timer;
public class CompletableApplyChained {
public static void main(String[] args) {
Timer timer = new Timer();
CompletableFuture<Machina> cf =
CompletableFuture.completedFuture(
new Machina(0))
.thenApply(Machina::work)
.thenApply(Machina::work)
.thenApply(Machina::work)
.thenApply(Machina::work);
System.out.println(timer.duration());
}
}
/* Output:
Machina0: ONE
Machina0: TWO
Machina0: THREE
Machina0: complete
514
*/
```
在这里,我们还添加了一个**Timer**它向我们展示每一步增加100毫秒还有一些额外的开销。
**CompletableFutures**的一个重要好处是它们鼓励使用私有子类原则(不分享任何东西)。默认情况下,使用**thenApply()**来应用一个不与任何人通信的函数 - 它只需要一个参数并返回一个结果。这是函数式编程的基础并且它在并发性方面非常有效。并行流和ComplempleFutures旨在支持这些原则。只要您不决定共享数据共享非常容易甚至意外您可以编写相对安全的并发程序。
<!-- Deadlock -->
## 死锁
<!-- Constructors are not Thread-Safe -->
## 构造函数非线程安全