写线程池的时候到底需不需要自己包装FutureTask
一般来说,我们会写一个Runnable接口的实现用来包装我们需要异步执行的内容。但是,Runnable是没有实现的,有些时候,我们需要执行的内容是一个有返回值的方法或者需要抛出异常,这个时候,我们就需要使用Callable接口。
简单来说,callable可以实现所有Runnable能够实现的功能。
由于我们需要在线程异步执行完成后返回一个结果,但是由于这个执行是异步的,那么,我们是没有办法立刻拿到结果的,这时Future方式就出现了。
Future实际上一个承诺,当我们调用时,返回一个Future对象,这个返回是立刻的,当线程的异步任务完成时,我们可以通过方法Future.get()
来获取真正的返回值。
在代码的编写过程中,我们可能见过以下两种例子
示例1
FutureTask<String> task = new FutureTask<String>(new MyCallable());
ExecutorService executorService = Executors.newFixedThreadPool(5);
executorService.submit(task);
String s = task.get();
System.out.println(s);
executorService.shutdown();
示例2
MyCallable callable = new MyCallable();
ExecutorService executorService Executors.newFixedThreadPool(5);
Future<String> future executorService.submit(callable);
String s = future.get();
System.out.println(s);
executorService.shutdown();
我们发现这两种方式实际上是相同的结果,这是我们就会十分迷惑,FutureTask与Future有什么区别?
首先,我们需要了解,Future实际上是一个接口,这个接口中有如下方法。
FutureTask实际上是实现了接口RunnableFuture,而RunnableFuture继承了Runnable, Future
public class FutureTask<V> implements RunnableFuture<V>
public interface RunnableFuture<V> extends Runnable, Future<V>
所以,我们知道实际上FutureTask实际上既是一个Future也是一个Callable,因此,它可以作为线程池的一个任务,通过submit(Callable)
方法提交给线程池进行处理。
一般来说,我们不需要对Callable进行包装,也就是上面示例1的方式并不是我们通常应该选择的方式。因为,实际上在线程池中已经将Callable包装为FutureTask了。
这里我们从源码中分析一下我们给线程池执行的Callable到底是怎么执行的?
首先,我们需要查看submit
方法
查看ThreadPoolExecutor
类,没有找到submit
方法,那么这个方法一定是在父类中
public class ThreadPoolExecutor extends AbstractExecutorService
接下来我们查看抽象类AbstractExecutorService
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
这里我们注意到在task执行前,有一个方法newTaskFor
的调用,这个方法返回了RunnableFuture
类的对象
public interface RunnableFuture<V> extends Runnable, Future<V>
FutureTask实际上是这里的RunnableFuture
的接口
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
我们看到newTaskFor
方法,实际上返回的就是FutureTask
,因此,我们根本无需再将Callable包装为FutureTask
,这个工作实际上重复的。
接下来的一篇中,我们将会分析真正执行任务的方法execute(ftask);
。