实现Runnable接口的线程类与一个缺陷,就是在任务执行完之后无法取得任务的返回值。 如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦 。所以,从JDK 1.5开始,java提供了Callable接口,该接口和Runnable接口相类似,提供了一个call()方法可以作为线程的执行体,但是call()方法要比run()方法更为强大:# call()方法可以有返回值;# call()方法可以声明抛出异常。那么使用callable接口是如何获取返回值的呢?
一、Callable与Runnable
既然Callable接口可以看做是Runnable的“增强版”,那我们先看看Runnable接口的实现,追根溯源也是搬砖的普遍素质嘛~
public interface Runnable {
public abstract void run();
}
Runnable接口中只包含一个抽象方法run()返回值为void, 所以在执行完任务之后无法返回任何结果。接下来我们可以看看Callable接口
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
这是一个泛型接口,call()函数返回的类型就是传递进来的V类型。 那么该如何使用Callable接口呢?Callable接口不是Runnable接口的子接口,所以Callable对象不能直接作为Thread的target去运行;而且call方法还有返回值--call()方法并不是直接调用。
JDK提供了Future接口来代表call()方法里的返回值,并且为Future接口提供了一个实现类FutureTask,该类实现了Future接口,并实现了Runnable接口,其实该类也是Future接口的唯一实现类,所以FutureTask对象可以作为Thread的target。Callable一般配合ExecutorService来使用的,在ExecutorService接口中声明了若干个submit方法的重载版本:
/**
* Submits a value-returning task for execution and returns a
* Future representing the pending results of the task. The
* Future's <tt>get</tt> method will return the task's result upon
* successful completion.
*/
<T> Future<T> submit(Callable<T> task);
/**
* Submits a Runnable task for execution and returns a Future
* representing that task. The Future's <tt>get</tt> method will
* return the given result upon successful completion.
*/
<T> Future<T> submit(Runnable task, T result);
/**
* Submits a Runnable task for execution and returns a Future
* representing that task. The Future's <tt>get</tt> method will
* return <tt>null</tt> upon <em>successful</em> completion.
*/
Future<?> submit(Runnable task);
一般情况下我们使用第一个submit方法和第三个submit方法,第二个submit方法很少使用 。
这里提一下ExecutorService的submit与execute方法的区别:ExecutorService的submit与execute方法都能执行任务,但在使用过程,发现其对待run方法抛出的异常处理方式不一样。两者执行任务最后都会通过Executor的execute方法来执行,但对于submit,会将runnable物件包装成FutureTask,其run方法会捕捉被包装的Runnable Object的run方法抛出的Throwable异常,待submit方法所返回的的Future Object调用get方法时,将执行任务时捕获的Throwable Object包装成java.util.concurrent.ExecutionException来抛出。
而对于execute方法,则会直接抛出异常,该异常不能被捕获,想要在出现异常时做些处理,可以实现Thread.UncaughtExceptionHandler接口。
当将一个Callable的对象传递给ExecutorService的submit方法,则该call方法自动在一个线程上执行,并且会返回执行结果Future对象。同样,将Runnable的对象传递给ExecutorService的submit方法,则该run方法自动在一个线程上执行,并且会返回执行结果Future对象,但是在该Future对象上调用get方法,将返回null。
二、Future接口
Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
Future类位于java.util.concurrent包下 :
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;
}
在Future接口中声明了5个方法,下面依次解释每个方法的作用:
- cancel方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数 mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返 回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若 mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论 mayInterruptIfRunning为true还是false,肯定返回true。
- isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
- isDone方法表示任务是否已经完成,若任务完成,则返回true;
- get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
- get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。
也就是说Future提供了三种功能:
1)判断任务是否完成;
2)能够中断任务;
3)能够获取任务执行结果。
因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了FutureTask。
三、FutureTask实现类
我们先来看一下FutureTask的实现:
public class FutureTask<V> implements RunnableFuture<V>
FutureTask类实现了RunnableFuture接口,我们再来看一下RunnableFuture接口的实现:
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
可以看出RunnableFuture继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
FutureTask提供了2个构造器:
public FutureTask(Callable<V> callable) {
}
public FutureTask(Runnable runnable, V result) {
}
如何有童鞋对FutureTask具体内容感兴趣,可以看一下这篇文章:FutureTask深入解析
四、使用示例
1.使用Callable+Future获取执行结果 :
public class CallableTest implements Callable<String>{
private int id;
public CallableTest(int ThreadId){
id = ThreadId;
}
public String call(){
return "knock knock,who's there ? This is Thead " + id;
}
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
ArrayList<Future<String>> results = new ArrayList<Future<String>>();
for(int i = 0; i < 10; i++){
results.add(exec.submit(new CallableTest(i)));
}
for(Future<String> fs : results){
try {
System.out.println(fs.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}finally{
exec.shutdown();
}
}
}
}
执行结果:
knock knock,who's there ? This is Thead 0
knock knock,who's there ? This is Thead 1
knock knock,who's there ? This is Thead 2
knock knock,who's there ? This is Thead 3
knock knock,who's there ? This is Thead 4
knock knock,who's there ? This is Thead 5
knock knock,who's there ? This is Thead 6
knock knock,who's there ? This is Thead 7
knock knock,who's there ? This is Thead 8
knock knock,who's there ? This is Thead 9
其中submit方法会产生Future对象,用Callable返回结果的特定类型进行了参数化。当然,也可以用isDone()来查询Future是否已经完成。当任务完成时,可以调用get()方法来获取该结果;也可以直接调用get()方法,该种情况下,get()将阻塞,直至结果准备就绪。
2.使用Callable+FutureTask获取执行结果 :
public class CallableTest implements Callable<String>{
private int id;
public CallableTest(int ThreadId){
id = ThreadId;
}
public String call(){
return "knock knock,who's there ? This is Thead " + id;
}
public static void main(String[] args) {
ArrayList<FutureTask<String>> results = new ArrayList<FutureTask<String>>();
for(int i = 0; i < 10; i++){
FutureTask<String> ft = new FutureTask<>(new CallableTest(i));
new Thread(ft).start();
results.add(ft);
}
for(Future<String> fs : results){
try {
System.out.println(fs.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
执行结果:
knock knock,who's there ? This is Thead 0
knock knock,who's there ? This is Thead 1
knock knock,who's there ? This is Thead 2
knock knock,who's there ? This is Thead 3
knock knock,who's there ? This is Thead 4
knock knock,who's there ? This is Thead 5
knock knock,who's there ? This is Thead 6
knock knock,who's there ? This is Thead 7
knock knock,who's there ? This is Thead 8
knock knock,who's there ? This is Thead 9
可以看到,使用FutureTask对象来获取返回结果的时候,该对象可以作为Thread中的target对象,所以可以不使用 ExecutorService
来提交任务(实际上ExecutorService可能要比new Thread().start()方式要慢一点
),但运行的结果还是一样的。
参考文章:
Java并发编程:Callable、Future和FutureTask