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
对象不能直接作为Thread
的target
如何获取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 方法的返回值。该方法让程序最多阻塞timeout 和unit 指定的时间,如果经过指定时间后 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 | import java.util.concurrent.*; |
上面程序中使用Lambda
表达式直接创建了Callable
对象,这样就无须先创建Callable
实现类,再创建Callable
对象了。实现Callable
接口与实现Runnable
接口并没有太大的差别,只是Callable
的call()
方法允许声明抛出异常,而且允许带返回值。
- 上面程序先使用
Lambda
表达式创建一个Callable
对象, - 然后将该实例包装成一个
FutureTask
对象。 - 主线程中当循环变量
i
等于20时,程序启动以FutureTask
对象为target
的线程。 - 程序最后调用
FutureTask
对象的get()
方法来返回call()
方法的返回值,get()
方法将导致主线程被阻塞,直到call()
方法结束并返回为止。
运行上面程序,将看到主线程和call()
方法所代表的线程交替执行的情形,程序最后还会输出call()
方法的返回值。
本文重点
创建并启动有返回值的线程的步骤如下。
- 创建
Callable
接口的实现类,并实现call()
方法,该call()
方法将作为线程执行体,且该call()
方法有返回值,再创建Callable
实现类的实例。从Java 8
开始,可以直接使用Lambda
表达式创建Callable
对象。 - 使用
FutureTask
类来包装Callable
对象,该FutureTask
对象封装了该Callable
对象的call()
方法的返回值. - 使用
FutureTask
对象作为Thread
对象的target
创建并启动新线程。 - 调用
FutureTask
对象的get()
方法来获得子线程执行结束后的返回值,get()
方法会阻塞调用它的线程(如,在主线程中调用get()
方法,则阻塞主线程)。