callable-future-futuretask.md 8.5 KB
Newer Older
沉默王二's avatar
nio  
沉默王二 已提交
1
---
沉默王二's avatar
沉默王二 已提交
2
title: 获取 Java 线程执行结果:Callable、Future和FutureTask
沉默王二's avatar
沉默王二 已提交
3
shortTitle: 获取线程的执行结果
沉默王二's avatar
沉默王二 已提交
4
description: 本文深入解释了如何在Java中使用Callable、Future和FutureTask来获取多线程执行结果。无论你是Java新手还是经验丰富的开发者,你都能在这里找到有用的信息和技巧。点击了解更多。
沉默王二's avatar
nio  
沉默王二 已提交
5
category:
沉默王二's avatar
沉默王二 已提交
6 7 8 9 10 11 12
  - Java核心
tag:
  - Java并发编程
head:
  - - meta
    - name: keywords
      content: Java, 多线程, Callable, Future, FutureTask, 线程执行结果
沉默王二's avatar
nio  
沉默王二 已提交
13 14
---

沉默王二's avatar
沉默王二 已提交
15
# 14.2 获取线程的执行结果
沉默王二's avatar
nio  
沉默王二 已提交
16

沉默王二's avatar
沉默王二 已提交
17
[在第一节:初步掌握 Java 多线程中](https://javabetter.cn/thread/wangzhe-thread.html),我们讲述了创建线程的 3 种方式,一种是直接继承 Thread,一种是实现 Runnable 接口,另外一种是实现 Callable 接口。
沉默王二's avatar
nio  
沉默王二 已提交
18

沉默王二's avatar
沉默王二 已提交
19
前 2 种方式都有一个缺陷:在执行完任务之后无法获取执行结果。
沉默王二's avatar
nio  
沉默王二 已提交
20

沉默王二's avatar
沉默王二 已提交
21
如果需要获取执行结果,就必须通过共享变量或者线程通信的方式来达到目的,这样使用起来就比较麻烦。
沉默王二's avatar
nio  
沉默王二 已提交
22

沉默王二's avatar
沉默王二 已提交
23
Java 1.5 提供了 Callable、Future、FutureTask,它们可以在任务执行完后得到执行结果,今天我们就来详细的了解一下。
沉默王二's avatar
nio  
沉默王二 已提交
24

沉默王二's avatar
沉默王二 已提交
25
### 01、Callable 与 Runnable
沉默王二's avatar
nio  
沉默王二 已提交
26

沉默王二's avatar
沉默王二 已提交
27
由于 `Runnable``run()` 方法的返回值为 void:
沉默王二's avatar
nio  
沉默王二 已提交
28 29 30 31 32 33 34

```java
public interface Runnable {
    public abstract void run();
}
```

沉默王二's avatar
沉默王二 已提交
35
所以在执行完任务之后无法返回任何结果。
沉默王二's avatar
nio  
沉默王二 已提交
36

沉默王二's avatar
沉默王二 已提交
37
Callable 位于 `java.util.concurrent` 包下,也是一个接口,它定义了一个 `call()` 方法:
沉默王二's avatar
nio  
沉默王二 已提交
38 39 40 41 42 43 44

```java
public interface Callable<V> {
    V call() throws Exception;
}
```

沉默王二's avatar
沉默王二 已提交
45
可以看到,`call()` 方法返回的类型是一个 V 类型的[泛型](https://javabetter.cn/basic-extra-meal/generic.html)
沉默王二's avatar
nio  
沉默王二 已提交
46

沉默王二's avatar
沉默王二 已提交
47 48 49 50 51 52 53 54 55
那怎么使用 Callable 呢?

一般会配合 ExecutorService 来使用。

ExecutorService 是一个接口,位于 `java.util.concurrent` 包下,它是 Java 线程池框架的核心接口,用来异步执行任务。它提供了一些关键方法用来进行线程管理。

![](https://cdn.tobebetterjavaer.com/stutymore/callable-future-futuretask-20230619105509.png)

下面的例子就用到了 ExecutorService 的 submit 方法。
沉默王二's avatar
nio  
沉默王二 已提交
56 57

```java
沉默王二's avatar
沉默王二 已提交
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
// 创建一个包含5个线程的线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);

// 创建一个Callable任务
Callable<String> task = new Callable<String>() {
    public String call() {
        return "Hello from " + Thread.currentThread().getName();
    }
};

// 提交任务到ExecutorService执行,并获取Future对象
Future[] futures = new Future[10];
for (int i = 0; i < 10; i++) {
    futures[i] = executorService.submit(task);
}

// 通过Future对象获取任务的结果
for (int i = 0; i < 10; i++) {
    System.out.println(futures[i].get());
}

// 关闭ExecutorService,不再接受新的任务,等待所有已提交的任务完成
executorService.shutdown();
沉默王二's avatar
nio  
沉默王二 已提交
81 82
```

沉默王二's avatar
沉默王二 已提交
83
我们通过 Executors 工具类来创建一个 ExecutorService,然后向里面提交 Callable 任务,然后通过 Future 来获取执行结果。
沉默王二's avatar
nio  
沉默王二 已提交
84

沉默王二's avatar
沉默王二 已提交
85
为了做对比,我们再来看一下使用 Runnable 的方式:
沉默王二's avatar
nio  
沉默王二 已提交
86

沉默王二's avatar
沉默王二 已提交
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
```java
// 创建一个包含5个线程的线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);

// 创建一个Runnable任务
Runnable task = new Runnable() {
    public void run() {
        System.out.println("Hello from " + Thread.currentThread().getName());
    }
};

// 提交任务到ExecutorService执行
for (int i = 0; i < 10; i++) {
    executorService.submit(task);
}
沉默王二's avatar
nio  
沉默王二 已提交
102

沉默王二's avatar
沉默王二 已提交
103 104 105 106 107 108 109
// 关闭ExecutorService,不再接受新的任务,等待所有已提交的任务完成
executorService.shutdown();
```

可以看到,使用 Runnable 的方式要比 Callable 的方式简单一些,但是 Callable 的方式可以获取执行结果,这是 Runnable 做不到的。

### 02、Future
沉默王二's avatar
nio  
沉默王二 已提交
110

沉默王二's avatar
沉默王二 已提交
111
在前面的例子中,我们通过 Future 来获取 Callable 任务的执行结果,那么 Future 是什么呢?
沉默王二's avatar
nio  
沉默王二 已提交
112

沉默王二's avatar
沉默王二 已提交
113
Future 位于 `java.util.concurrent` 包下,它是一个接口:
沉默王二's avatar
nio  
沉默王二 已提交
114 115 116 117 118 119 120 121 122 123 124 125

```java
public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}
```

沉默王二's avatar
沉默王二 已提交
126
一共声明了 5 个方法:
沉默王二's avatar
nio  
沉默王二 已提交
127

沉默王二's avatar
沉默王二 已提交
128 129 130
- `cancel()` 方法用来取消任务,如果取消任务成功则返回 true,如果取消任务失败则返回 false。参数 mayInterruptIfRunning 表示是否允许取消正在执行却没有执行完毕的任务,如果设置 true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论 mayInterruptIfRunning 为 true 还是 false,此方法肯定返回 false,即如果取消已经完成的任务会返回 false;如果任务正在执行,若 mayInterruptIfRunning 设置为 true,则返回 true,若 mayInterruptIfRunning 设置为 false,则返回 false;如果任务还没有执行,则无论 mayInterruptIfRunning 为 true 还是 false,肯定返回 true。
- `isCancelled()` 方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
- `isDone()` 方法表示任务是否已经完成,若任务完成,则返回 true;
沉默王二's avatar
nio  
沉默王二 已提交
131 132 133 134 135
- `get()`方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
- `get(long timeout, TimeUnit unit)`用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回 null。

也就是说 Future 提供了三种功能:

沉默王二's avatar
沉默王二 已提交
136 137 138
- 1)判断任务是否完成;
- 2)能够中断任务;
- 3)能够获取任务执行结果。
沉默王二's avatar
nio  
沉默王二 已提交
139

沉默王二's avatar
沉默王二 已提交
140
由于 Future 只是一个接口,如果直接 new 的话,编译器是会有一个 ⚠️ 警告的,它会提醒我们最好使用 FutureTask。
沉默王二's avatar
nio  
沉默王二 已提交
141

沉默王二's avatar
沉默王二 已提交
142
![](https://cdn.tobebetterjavaer.com/stutymore/callable-future-futuretask-20230619111245.png)
沉默王二's avatar
nio  
沉默王二 已提交
143

沉默王二's avatar
沉默王二 已提交
144
实际上,FutureTask 是 Future 接口的一个唯一实现类,我们在前面的例子中 `executorService.submit()` 返回的就是 FutureTask,通过 debug 模式可以观察到。
沉默王二's avatar
nio  
沉默王二 已提交
145

沉默王二's avatar
沉默王二 已提交
146
![](https://cdn.tobebetterjavaer.com/stutymore/callable-future-futuretask-20230619111830.png)
沉默王二's avatar
nio  
沉默王二 已提交
147

沉默王二's avatar
沉默王二 已提交
148 149 150
### 03、FutureTask

我们来看一下 FutureTask 的实现:
沉默王二's avatar
nio  
沉默王二 已提交
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174

```java
public class FutureTask<V> implements RunnableFuture<V>
```

FutureTask 类实现了 RunnableFuture 接口,我们看一下 RunnableFuture 接口的实现:

```java
public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}
```

可以看出 RunnableFuture 继承了 Runnable 接口和 Future 接口,而 FutureTask 实现了 RunnableFuture 接口。所以它既可以作为 Runnable 被线程执行,又可以作为 Future 得到 Callable 的返回值。

FutureTask 提供了 2 个构造器:

```java
public FutureTask(Callable<V> callable) {
}
public FutureTask(Runnable runnable, V result) {
}
```

沉默王二's avatar
沉默王二 已提交
175
来个例子
沉默王二's avatar
nio  
沉默王二 已提交
176 177

```java
沉默王二's avatar
沉默王二 已提交
178 179 180 181 182 183 184 185 186 187 188 189
// 创建一个固定大小的线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);

// 创建一系列 Callable
Callable<Integer>[] tasks = new Callable[5];
for (int i = 0; i < tasks.length; i++) {
    final int index = i;
    tasks[i] = new Callable<Integer>() {
        @Override
        public Integer call() throws Exception {
            TimeUnit.SECONDS.sleep(index + 1);
            return (index + 1) * 100;
沉默王二's avatar
nio  
沉默王二 已提交
190
        }
沉默王二's avatar
沉默王二 已提交
191
    };
沉默王二's avatar
nio  
沉默王二 已提交
192
}
沉默王二's avatar
沉默王二 已提交
193 194 195 196 197 198

// 将 Callable 包装为 FutureTask,并提交到线程池
FutureTask<Integer>[] futureTasks = new FutureTask[tasks.length];
for (int i = 0; i < tasks.length; i++) {
    futureTasks[i] = new FutureTask<>(tasks[i]);
    executorService.submit(futureTasks[i]);
沉默王二's avatar
nio  
沉默王二 已提交
199 200
}

沉默王二's avatar
沉默王二 已提交
201 202 203 204
// 获取任务结果
for (int i = 0; i < futureTasks.length; i++) {
    System.out.println("Result of task" + (i + 1) + ": " + futureTasks[i].get());
}
沉默王二's avatar
nio  
沉默王二 已提交
205

沉默王二's avatar
沉默王二 已提交
206 207
// 关闭线程池
executorService.shutdown();
沉默王二's avatar
nio  
沉默王二 已提交
208 209
```

沉默王二's avatar
沉默王二 已提交
210
来看一下输出结果
沉默王二's avatar
nio  
沉默王二 已提交
211

沉默王二's avatar
沉默王二 已提交
212 213 214 215 216 217
```
Result of task1: 100
Result of task2: 200
Result of task3: 300
Result of task4: 400
Result of task5: 500
沉默王二's avatar
nio  
沉默王二 已提交
218 219
```

沉默王二's avatar
沉默王二 已提交
220
---
沉默王二's avatar
nio  
沉默王二 已提交
221

沉默王二's avatar
沉默王二 已提交
222
GitHub 上标星 8700+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括 Java 基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM 等等,共计 32 万余字,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 8700+ 的 Java 教程](https://javabetter.cn/overview/)
沉默王二's avatar
沉默王二 已提交
223 224

微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
沉默王二's avatar
nio  
沉默王二 已提交
225

沉默王二's avatar
沉默王二 已提交
226
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/gongzhonghao.png)