java并发编程:Callable和Future原理与应用

在java线程使用上,他的常用接口是Runable。这个接口只有一个方法那就是run()方法,这个方法没有入参没有返回参数。如果我们想在线程运行完后获取运行的结果,那么一定要了解CallableFuture了。

Callable

Callable其实是一个接口,它提供了一个可以返回确定类型实例的方法V call() throws Exception;。讲道理,我们自己也可以写一个接口做这么个事,但是官方在实现上有许多对该接口的支援,所以最好用他自己的。

在这个接口的说明里有:

1
2
3
4
5
A task that returns a result and may throw an exception. Implementors define a single method with no arguments called call.

The Callable interface is similar to Runnable, in that both are designed for classes whose instances are potentially executed by another thread. A Runnable, however, does not return a result and cannot throw a checked exception.

The Executors class contains utility methods to convert from other common forms to Callable classes.

翻译如下:

1
2
3
返回结果并且可能抛出异常的任务。实现者定义了一个不带任何参数的叫做 call 的方法。
Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable 不会返回结果,并且无法抛出经过检查的异常。
Executors 类包含一些从其他普通形式转换成 Callable 类的实用方法。

在应用中,通常需要我们写一个实现该接口的类。

Future

Callable接口相比Runable能够给我们两样东西,一个是返回结果,另一个是能够让我们能够捕获异常。Callable在线程中执行,由于它是异步的,需要有一个东西去接收线程执行的状态和结果。这个东西就是Future接口。

这个接口能够获取运行的状态,如果没有在运行中可以取消运行,可以获取运行结果。

接口说明:

1
A Future represents the result of an asynchronous computation. Methods are provided to check if the computation is complete, to wait for its completion, and to retrieve the result of the computation. The result can only be retrieved using method get when the computation has completed, blocking if necessary until it is ready. Cancellation is performed by the cancel method. Additional methods are provided to determine if the task completed normally or was cancelled. Once a computation has completed, the computation cannot be cancelled. If you would like to use a Future for the sake of cancellability but not provide a usable result, you can declare types of the form Future<?> and return null as a result of the underlying task.

翻译如下:

1
Future 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。计算完成后只能使用 get 方法来获取结果,如有必要,计算完成前可以阻塞此方法。取消则由 cancel 方法来执行。还提供了其他方法,以确定任务是正常完成还是被取消了。一旦计算完成,就不能再取消计算。如果为了可取消性而使用 Future 但又不提供可用的结果,则可以声明 Future<?> 形式类型、并返回 null 作为底层任务的结果。

同时说明里提供了一个CallableFuture的配合的代码(经过整理):

1
2
3
4
5
6
7
8
9
ExecutorService executor = Executors.newFixedThreadPool(20);

Future<String> future = executor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return "ok";
}
});
System.out.println(future.get());

FutureTask

Future接口不需要我们实现,但是不能在只实现Executor接口的线程池中运行,因为这个接口只接受Runable接口。

聪明的你一定能想到几种方法把Callable接口转接成Runable接口。例如,写一个类实现Runable,但是包含一个Callable的实例和一个生成结果后的回调接口,在Runable接口的run方法中运行Callable接口的call方法,得到结果后调用回调接口的方法将结果传出去。

另一种方式,包含一个Callable的实例和一个结果类型实例,同时这个类在run方法运行前后有状态标记,当运行成功后,将结果保存在这个结果类型实例中。外部的方法通过获取这个类实例的状态标记得知运行结果,成功则把结果拿出来。

以上方法想想都累,幸好官方提供了一个类,实现了后者的功能,那就是FutureTask

FutureTask间接实现了RunableFuture。也就是说它即可以run也可以有Future的功能。他的构造方法入参可以有FutureTask(Callable<V> callable),也可以有FutureTask(Runnable runnable, V result)

Callable好理解,就是上边说的方法。但是Runable是怎么回事呢?原来Executors类提供了一个RunableCallable的适配器。适配器很简单,源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* A callable that runs given task and returns given result
*/
static final class RunnableAdapter<T> implements Callable<T> {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
}

FutureTask拥有几种状态:

1
2
3
4
5
6
7
private static final int NEW          = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;

状态转移情况如下:

1
2
3
4
* NEW -> COMPLETING -> NORMAL
* NEW -> COMPLETING -> EXCEPTIONAL
* NEW -> CANCELLED
* NEW -> INTERRUPTING -> INTERRUPTED

  • 在创建这个类时,都是NEW状态。
  • 一旦这个类被线程执行run方法,则会进入COMPLETING状态,如果正常执行完,就是NORMAL状态,如果抛异常了就是EXCEPTIONAL状态。
  • 如果还没执行时,调用了cancel取消方法,则状态变为CANCELLED。
  • 如果在执行时取消,且线程允许被中断,则状态变为INTERRUPTING,当中断完成状态则变为INTERRUPTED。

在这个类中,最重要的方法就是get方法,当把这个类实例提交到线程运行后,我们最常用的方法就是get了。get方法默认会无限等待线程运行完毕,同时,这个类也提供了指定等待时间的get方法。

这里来写一个FutureTask的常用方式:

1
2
3
4
5
6
7
8
9
ExecutorService executor = Executors.newFixedThreadPool(20);
FutureTask<String> futureTask = new FutureTask<String>(new Callable<String>() {
@Override
public String call() throws Exception {
return "OK";
}
});
executor.execute(futureTask);
System.out.println(future.get());

应用

在一个方法中可能需要做N件事拿到N个结果,这N件事之间没有顺序关系,我们想尽快的做完这些事并得到结果。Runable其实在这个时候并不符合此时的业务需要,此时需要的是,同时执行N件事,然后等待N件事都做完,拿到结果。这时,FutureTask就需要出场了,只需要在把所有任务提交后,调用每个任务的get方法等待执行完成即可。