并发编程之Callable、Future和FutureTask

本篇文章介绍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()的执行过程如下:

  1. 执行在构造函数中指定的任务(Callable.call())。
  2. 以原子方式来更新同步状态(调用AQS.compareAndSetState()方法,设置state为执行完成状态)。如果这个原子操作成功,就设置代表计算结果的变量result的值为Callable.call()的返回值,然后调用AQS.releaseShared()。
  3. AQS.releaseShared()首先会回调在子类Sync中实现的tryReleaseShared()来执行release操作(设置运行任务的线程runner为null,然后返回true);AQS.releaseShared(),然后唤醒线程等待队列中的第一个线程。
  4. 调用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()方法返回。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值