6. Callable&Future 接口
6.1 Callable 接口
通过Runable创建线程。但是,Runable缺少的一项功能是,当线程终止时(即 run() 完成时),我们无法是线程返回结果,为了支持此功能,Java提供了Callable接口。
- 为了实现Runable,需要实现不返回任何内容的
run()
方法,而对于Callable,需要实现在完成时返回结果的call()
方法。请注意,不能使用Callable创建线程,只能使用Runable创建线程。 - 另一个区别是
call()
方法可以引发异常,而run()
则不能。 - 为实现Callable而必须去重写call方法
public class CallableExample implements Callable {
@Override
public Object call() throws Exception {
String uuid = UUID.randomUUID().toString();
return uuid;
}
}
6.2 Future 接口
当call()
方法完成是,结果必须存储在主线程已知对象中,以便主线程中可以知道
该线程返回的结果,为此,可以使用Future对象。将Future视为保存结果的对象-它可能暂时不保存结果,但将来会保存(一旦Callable返回)。
因此,Future基本上是主线程可以跟踪进度以及其他线程的结果的一个种方式。要实现此接口,必须重写5种方法,下面累出重要的方法。
/**
* 用于停止任务。
* 如果尚未启动,它将停止任务。如果已经启动,则仅在mayInterrupt为true时才会中断任
* 任务
*/
public boolean cancel(boolean mayInterruptIfRunning);
/**
* 抛出InterruptedException,ExecutionException
* 用于获取任务结果。如果任务完成,它将立即返回结果,否则将等待任务完成,然后返回
* 结果。
*/
public V get();
/**
* 如果任务完成,则返回true,否则返回false
*
*/
public boolean isDone();
可以看到Callable和Future做两件事情,Callable与Runable类似,因为它封装了要在另一个线程上运行的任务,而Future用于存储从另一个线程获取得的结果。实际上,future也可以与Runable一起使用。
要创建线程,需要Runable。为了获得结果,需要future。
Java库具有具体的FutureTask类型,该类型实现Runable和Future,并方便地将两种功能结合在一起。
可以通过为其构造函数提供的Callable来创建FutureTask。然后将,FutureTask对象提供给Thread的构造函数以创建Thread对象,因此,间接使用Callable创建线程。
public class TestCallable implements Callable<Object> {
private int taskNum;
public TestCallable(int taskNum) {
this.taskNum = taskNum;
}
private static void test() throws ExecutionException, InterruptedException {
System.out.println("----程序开始运行----");
Date date1 = new Date();
int taskSize = 5;
FutureTask[] randomNumberTasks = new FutureTask[5];
List<Future> futureList = new ArrayList<>();
for (int i = 0; i < taskSize; i++) {
randomNumberTasks[i] = new FutureTask(new TestCallable(i));
new Thread(randomNumberTasks[i]).start();
}
// 获取所有并发任务运行结果
for (FutureTask randomNumberTask : randomNumberTasks) {
System.out.println(">>>"+randomNumberTask.get().toString());
}
Date date2=new Date();
System.out.println("----程序结束运行----,程序运行时间【" + (date2.getTime() - date1.getTime()) + "毫秒】");
}
@Override
public Object call() throws Exception {
System.out.println(">>>" + taskNum + "任务启动");
Date dateTmpl = new Date();
Thread.sleep(1000);
Date dateTmpl2 = new Date();
long time = dateTmpl2.getTime() - dateTmpl.getTime();
System.out.println(">>>" + taskNum + "任务终止");
return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
test();
}
}
结果:
----程序开始运行----
>>>2任务启动
>>>4任务启动
>>>0任务启动
>>>3任务启动
>>>1任务启动
>>>3任务终止
>>>4任务终止
>>>1任务终止
>>>2任务终止
>>>0任务终止
>>>0任务返回运行结果,当前任务时间【1013毫秒】
>>>1任务返回运行结果,当前任务时间【1012毫秒】
>>>2任务返回运行结果,当前任务时间【1013毫秒】
>>>3任务返回运行结果,当前任务时间【1013毫秒】
>>>4任务返回运行结果,当前任务时间【1013毫秒】
----程序结束运行----,程序运行时间【1014毫秒】
6.2 小结
在主线程中需要执行比较耗操作时,但又不想阻塞主线程时,可以把这些作业交给Future对象在后台完成,党主线程将来需要时,就可以通过Future对象获得后台作业计算结果或执行状态。