0%

16.2.3 使用Callable和Future创建线程

16.2.3 使用Callable和Future创建线程

通过实现Runnable接口创建多线程时, Thread类的作用就是把Runnable实例提供的run()方法包装成线程执行体。

Callable接口简介

Java 5开始,Java提供了Callable接口,可以把该接口看成Runnable接口的增强版, Callable接口提供了一个call()方法可以作为线程执行体,但call()方法比run()方法功能更强大,具体表现为:

  • call()方法可以有返回值。
  • call()方法可以声明抛出异常。

Callable接口实例不能直接作为Thread的target

Callable接口是Java 5新增的接口,但Callable接口不是Runnable接口的子接口,所以Callable对象不能直接作为Threadtarget

如何获取Callable接口中call()方法的返回值

Java 5提供了Future接口来代表Callable接口里call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该实现类实现了Future接口,并实现了Runnable接口,所以FutureTask可以作为Thread类的target

Future接口常用方法

Future接口里定义了如下几个公共方法来控制它关联的Callable任务。

方法 描述
boolean cancel(boolean mayInterruptIfRunning) 试图取消该Future里关联的Callable任务。
V get() 返回Callable任务里call方法的返回值。调用该方法将导致程序阻塞,必须等到子线程结束后才会得到返回值。
V get(long timeout, TimeUnit unit) 返回 Callable任务里call方法的返回值。该方法让程序最多阻塞timeoutunit指定的时间,如果经过指定时间后 Callable任务依然没有返回值,将会抛出TimeoutException异常。
boolean isCancelled() 如果在Callable任务正常完成前被取消,则返回true
boolean isDone() 如果Callable任务已完成,则返回true
## Callable接口是函数式接口且有泛型限制 ##
Callable接口有泛型限制, Callable接口里的泛型形参类型与call()方法返回值类型相同且Callable接口是函数式接口,因此可使用Lambda表达式创建Callable对象。
## 创建并启动有返回值的线程的步骤 ##
创建并启动有返回值的线程的步骤如下。
1. 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,且该call()方法有返回值,再创建Callable实现类的实例。从Java 8开始,可以直接使用Lambda表达式创建Callable对象。
2. 使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值.
3. 使用FutureTask对象作为Thread对象的target创建并启动新线程。
4. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

程序示例

下面程序通过实现Callable接口来实现线程类,并启动该线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import java.util.concurrent.*;

public class ThirdThread
{
public static void main(String[] args)
{
// 1.先使用Lambda表达式创建Callable<Integer>对象
// 2.使用FutureTask来包装Callable对象
FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)() -> {
int i = 0;
for ( ; i < 100 ; i++ )
{
System.out.println(Thread.currentThread().getName()
+ " 的循环变量i的值:" + i);
}
// call()方法可以有返回值
return i;
});

for (int i = 0 ; i < 100 ; i++)
{
System.out.println(Thread.currentThread().getName()
+ " 的循环变量i的值:" + i);
if (i == 20)
{
// 3.实质还是以Callable对象来创建、并启动线程
new Thread(task , "有返回值的线程").start();
}
}
try
{
// 4.获取线程返回值
System.out.println("子线程的返回值:" + task.get());
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
}

上面程序中使用Lambda表达式直接创建了Callable对象,这样就无须先创建Callable实现类,再创建Callable对象了。实现Callable接口与实现Runnable接口并没有太大的差别,只是Callablecall()方法允许声明抛出异常,而且允许带返回值。

  • 上面程序先使用Lambda表达式创建一个Callable对象,
  • 然后将该实例包装成一个FutureTask对象。
  • 主线程中当循环变量i等于20时,程序启动以FutureTask对象为target的线程。
  • 程序最后调用FutureTask对象的get()方法来返回call()方法的返回值,get()方法将导致主线程被阻塞,直到call()方法结束并返回为止。

运行上面程序,将看到主线程和call()方法所代表的线程交替执行的情形,程序最后还会输出call()方法的返回值。

本文重点

创建并启动有返回值的线程的步骤如下。

  1. 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,且该call()方法有返回值,再创建Callable实现类的实例。从Java 8开始,可以直接使用Lambda表达式创建Callable对象。
  2. 使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值.
  3. 使用FutureTask对象作为Thread对象的target创建并启动新线程。
  4. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值,get()方法会阻塞调用它的线程(如,在主线程中调用get()方法,则阻塞主线程)。

原文链接: 16.2.3 使用Callable和Future创建线程