本篇文章介绍Callable、Future和FutureTask,部分内容总结摘抄自《Java并发编程的艺术》,仅作笔记。
我们要使用多线程时首先想到的是实现Runnable接口或继承Thread类,但由于他们的run()方法的返回值是void,因此我们无法在线程执行完毕后获取到执行结果。而Callable和Future接口不仅可以获取线程的执行结果,还可以取消任务和查询线程是否执行完成等。
Callable接口
Callable接口与Runnable接口类似,它们都是为其实例可能由另一个线程执行的类设计的,区别在于Runnable不会返回结果和抛异常。Callable接口只有一个call()方法,返回值为传递进来的泛型V,这个方法可以抛出异常。
public interface Callable<V> {
V call() throws Exception;
}
Future接口
Future接口就是为Callable接口服务的,我们通过Future接口可以取消对应Callable定义的任务以及获取线程执行结果等。Future接口的方法如下:
//尝试取消此任务,取消成功返回true,否则返回false。如果该任务已经完成,已经被取消或由于其他原因
//无法被取消,则返回false;如果该任务尚未运行,则返回true;如果该任务已经在运行且没有运行完成,
//当参数mayInterruptIfRunning为true时,则取消任务,返回true,否则允许该任务运行完成,返回false
boolean cancel(boolean mayInterruptIfRunning);
//获取执行结果,该方法一直阻塞直到线程执行完成
V get() throws InterruptedException, ExecutionException;
//获取执行结果,该方法一直阻塞直到线程执行完成或超时直接返回null
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
//如果该任务在正常完成之前被取消,则返回true,否则返回false
boolean isCancelled();
//返回该任务是否执行完成,执行完成返回true,否则返回false
boolean isDone();
介绍了以上两个接口的方法是看不出Callable接口是怎么与Future接口绑定在一起的,原因在于它们两个通常是与ExecutorService接口一起使用的,ExecutorService接口提供了以下方法:
//提交一个Callable任务,并返回一个代表任务处理结果的Future
<T> Future<T> submit(Callable<T> task);
关于这两个接口我们现在只需要知道是这样绑定的就可以了,在介绍线程池的时候会详细介绍,此处不再赘述。
FutureTask类
FutureTask类实现了Runnable接口和Callable接口,因此它既可以当作Runnable被其他线程执行,又可以作为Future来代理Callable的执行结果。FutureTask在Executor框架中表示异步任务,可以用来表示一些时间较长的计算,这些计算可以在使用计算结果之前启动。
FutureTask类的构造函数如下:
//传入Callable
public FutureTask(Callable<V> callable);
//传入Runnable,并指定一个get()方法返回的对象
public FutureTask(Runnable runnable, V result);
FutureTask类除了实现Callable接口的方法,还有以下方法:
//FutureTask无论是交给Thread执行还是线程池执行,执行的都是run()方法
public void run();
//保证run()方法运行后重置到初试状态
protected boolean runAndReset();
Future.get()方法的行为取决于任务的状态。如果任务已经完成,那么get()会立即返回结果,否则get()将阻塞直到任务进入完成状态,然后返回结果或者抛出异常。FutureTask将计算结果从执行计算的线程传递到获取这个结果的线程,而FutureTask的规范确保了这种传递过程能实现结果的安全发布。
FutureTask可以处于以下3种状态:
- 未启动:当创建一个FutureTask,且没有执行FutureTask.run()方法之前,这个FutureTask就处于未启动状态。
- 已启动:FutureTask.run()方法被执行的过程中,FutureTask就处于已启动状态。
- 已完成:FutureTask.run()方法执行完后正常结束,或调用FutureTask.cancel()取消,或执行run()方法时抛出异常结束,FutureTask就处于已完成状态。
FutureTask的状态迁移示意图如下:
当FutureTask处于未启动或已启动状态时,执行FutureTask.get()方法将导致调用线程阻塞;当FutureTask处于已完成状态时,执行FutureTask.get()方法会立刻返回执行结果或抛出异常。
当FutureTask处于未启动状态时,执行FutureTask.cancel()方法将导致此任务永远不会被执行;当FutureTask处于已启动状态时,执行FutureTask.cancel(true)方法将以中断执行此任务线程的方式来试图停止任务;当FutureTask处于已启动状态时,执行FutureTask.cancel(false)方法将不会对正在执行此任务的线程产生影响;当FutureTask处于已完成状态时,执行FutureTask.cancel()方法将直接false。
get()和cancel()方法的执行示意图如下:
FutureTask的使用
可以把FutureTask交给Executor执行,也可以通过ExecutorService.submit()方法返回一个FutureTask,然后执行FutureTask.get()方法或者FutureTask.cancel()方法。
使用Callable接口和Future接口的例子如下:
public class CallableTest {
public static void main(String[] args) {
//线程池
ExecutorService executor = Executors.newCachedThreadPool();
//创建Callable
Task task = new Task();
//提交任务并获取执行结果
Future<Integer> future = executor.submit(task);
//关闭线程池
executor.shutdown();
System.out.println("任务是否执行完成:"+future.isDone());
try {
System.out.println("任务执行结果:"+future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("调用get()方法后任务是否执行完成:"+future.isDone());
}
}
class Task implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("开始执行任务...");
int count = 0;
Thread.sleep(3000);
for (int i=0;i<3000;i++){
count++;
}
System.out.println("任务完成了...");
return count;
}
}
在前面提到过FutureTask可以当作Runnable和Callable使用,下面使用Callable和FutureTask且FutureTask当作Runnable的例子:
public class FutureTaskTest {
public static void main(String[] args) {
//创建Callable
Task task = new Task();
//创建FutureTask
FutureTask<Integer> futureTask = new FutureTask<>(task);
Thread thread = new Thread(futureTask);
//这里的start()方法真正指定的是FutureTask的run()方法
thread.start();
System.out.println("任务是否执行完成:"+futureTask.isDone());
try {
System.out.println("任务执行结果:"+futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("调用get()方法后任务是否执行完成:"+futureTask.isDone());
}
}
FutureTask的实现
FutureTask的实现基于AQS。java.util.cocurrent包中的很多可阻塞类都是基于AQS实现的。AQS是一个同步框架,它提供通用机制来原子性管理同步状态、阻塞和唤醒线程,以及维护被阻塞线程的队列。
每一个基于AQS实现的同步器都会包含两种类型的操作,如下:
- 至少一个acquire操作。这个操作阻塞调用线程,直到AQS的状态允许这个线程继续执行。FutureTask的acquire操作为get()方法调用。
- 至少一个release操作。这个操作改变AQS的状态,改变后的状态可允许一个或多个阻塞线程被接触阻塞。FutureTask的release操作包括run()方法和cancel()方法。
基于“复合优先于继承”的原则,FutureTask声明了一个内部私有的继承于AQS的子类Sync,对FutureTask所有公有方法的调用都会委托给这个内部子类。事实上基于AQS实现的同步器都是这样使用的,区别只是具体实现不同。
AQS被作为“模版方法模式”的基础类提供给FutureTask的内部子类Sync,这个内部类只需要实现状态检查和状态更新的方法即可,这些方法将控制FutureTask的获取和释放操作。具体来说,Sync实现了AQS的tryAcquireShared()方法和tryReleaseShared()方法,Sync通过这两个方法检查和更新同步状态。
FutureTask的设计示意图如下图:
如图所示,Sync是FutureTask的内部私有类,它继承自AQS。创建FutureTash时会创建内部私有的成员对象Sync,FutureTask所有的公有方法都直接委托给了内部私有的Sync。
FutureTask.run()的执行过程如下:
- 执行在构造函数中指定的任务(Callable.call())。
- 以原子方式来更新同步状态(调用AQS.compareAndSetState()方法,设置state为执行完成状态)。如果这个原子操作成功,就设置代表计算结果的变量result的值为Callable.call()的返回值,然后调用AQS.releaseShared()。
- AQS.releaseShared()首先会回调在子类Sync中实现的tryReleaseShared()来执行release操作(设置运行任务的线程runner为null,然后返回true);AQS.releaseShared(),然后唤醒线程等待队列中的第一个线程。
- 调用FutureTask.done()。
当执行FutureTask.get()方法时,如果FutureTask不是处于执行完成状态或已取消状态,当前执行线程将到AQS的线程等待队列中等待。当某个线程执行FutureTask.run()方法或FutureTask.cancel()方法时,会唤醒线程等待队列的第一个线程。
假设开始时FutureTask处于未启动或已启动状态,等待队列中已经有3个线程(A、B和C)在等待。此时,线程D执行get()方法将导致线程D也到等待队列中去等待。
当线程E执行run()方法时,会唤醒队列中的第一个线程A。线程A被唤醒后,首先把自己从队列中删除,然后唤醒它的后继线程B,最后线程A从get()方法返回。线程B、C和D重复线程A的处理流程。最终,在队列中等待的所有线程都被级联唤醒并从get()方法返回。