FutureTask 详解

目录

FutureTask 简介

FutureTask 的使用

FutureTask 的实现

FutureTask.get()方法

FutureTask.run()方法


Future 接口和实现 Future 接口的 FutureTask 类,代表异步计算的结果。

FutureTask 简介

        FutureTask 除了实现 Future 接口外,还实现了 Runnable 接口。因此,FutureTask 可以交给 Executor 执行,也可以由调用线程直接执行(FutureTask.run())。

根据 FutureTask.run()方法被执行的时机,FutureTask 可以处于下面 3 种状态。

1) 未启动: 当创建一个 FutureTask,且没有执行 FutureTask.run()方法之前,这个 FutureTask 处于未启动状态。

2) 已启动:FutureTask.run()方法被执行的过程中,FutureTask 处于已启动状态。

3) 已完成:FutureTask.run()方法执行完后正常结束,或被取消(FutureTask.cancel (…)),或执行 FutureTask.run()方法时抛出异常而异常结束,FutureTask 处于已完成状态。

FutureTask 的状态迁移示意图

  • 当 FutureTask 处于未启动或已启动状态时,执行 FutureTask.get()方法将导致调用线程阻塞;
  • 当 FutureTask 处于已完成状态时,执行 FutureTask.get()方法将导致调用线程立即返回结果或抛出异常。
  • 当 FutureTask 处于未启动状态时,执行 FutureTask.cancel()方法将导致此任务永远不会被执行;
  • 当 FutureTask 处于已启动状态时,执行 FutureTask.cancel(true)方法将以中断执行此任务线程的方式来试图停止任务;
  • 当 FutureTask 处于已启动状态时,执行 FutureTask.cancel(false)方法将不会对正在执行此任务的线程产生影响(让正在执行的任务运行完成);
  • 当 FutureTask 处于已完成状态时,执行 FutureTask.cancel(…)方法将返回 false。
FutureTask 的 get 和 cancel 的执行示意图

FutureTask 的使用

当一个线程需要等待另一个线程把某个任务执行完后它才能继续执行,此时可以使用FutureTask。

假设有多个线程执行若干任务,每个任务最多只能被执行一次。当多个线程试图同时执行同一个任务时,只允许一个线程执行任务,其他线程需要等待这个任务执行完后才能继续执行。

private final ConcurrentMap<Object, Future<String>> taskCache = new
        ConcurrentHashMap<Object, Future<String>>();
private String executionTask(final String taskName) throws ExecutionException,
        InterruptedException {
    while (true) {
        Future<String> future = taskCache.get(taskName); // 1.1,2.1
        if (future == null) {
            Callable<String> task = new Callable<String>() {
                public String call() throws InterruptedException {
                    return taskName;
                }
            };
            FutureTask<String> futureTask = new FutureTask<String>(task);
            // 多线程竞争,第一个线程获取Null值,以后只能获取对应的Future对象
            future = taskCache.putIfAbsent(taskName, futureTask); // 1.3
            if (future == null) {
                future = futureTask;
                futureTask.run(); // 1.4 执行任务
            }
        }
        try {
            return future.get(); // 1.5,
        } catch (CancellationException e) {
            taskCache.remove(taskName, future);
        }
    }
}

当两个线程试图同时执行同一个任务时,如果 Thread 1 执行 1.3 后 Thread 2 执行 2.1,那么接下来 Thread 2 将在 2.2 等待,直到 Thread 1 执行完 1.4 后 Thread 2 才能从 2.2(FutureTask.get())返回。

FutureTask 的实现

FutureTask 的实现基于 AbstractQueuedSynchronizer(以下简称为 AQS)。AQS 是一个同步框架,它提供通用机制来原子性管理同步状态、阻塞和唤醒线程,以及 维护被阻塞线程的队列。

基于 AQS 实现的同步器包括: ReentrantLock、Semaphore、ReentrantReadWriteLock、 CountDownLatch 和 FutureTask。

每一个基于 AQS 实现的同步器都会包含两种类型的操作,如下。

1) 至少一个 acquire 操作:这个操作阻塞调用线程,除非/直到 AQS 的状态允许这个线程继续执行。FutureTask 的 acquire 操作为 get()/get(long timeout,TimeUnit unit)方法调用。

2) 至少一个 release 操作:这个操作改变 AQS 的状态,改变后的状态可允许一个或多个阻塞线程被解除阻塞。FutureTask 的 release 操作包括 run()方法和 cancel (…)方法。

基于“复合优先于继承”的原则,FutureTask 声明了一个内部私有的继承于 AQS 的子类 Sync,对 FutureTask 所有公有方法的调用都会委托给这个内部子类。

Sync 实现了 AQS 的 tryAcquireShared(int)方法和 tryReleaseShared (int)方法,Sync 通过这两个方法来检查和更新同步状态。

FutureTask 的设计示意图

 Sync 是 FutureTask 的内部私有类,它继承自 AQS。创建 FutureTask 时会 创建内部私有的成员对象 Sync,FutureTask 所有的的公有方法都直接委托给了内部私有 的 Sync。

FutureTask.get()方法

调用 AQS.acquireSharedInterruptibly(int arg)方法,这个方法的执行过程如下。

1) 调用 AQS.acquireSharedInterruptibly(int arg)方法,这个方法首先会回调在子类 Sync 中实现的 tryAcquireShared()方法来判断 acquire 操作是否可以成功。

acquire 操作可以成功的条件为: state 为执行完成状态 RAN 或已取消状态 CANCELLED,且 runner 不为 null。

2) 如果成功则 get()方法立即返回。如果失败则到线程等待队列中去等待其他线程执行 release 操作。

3) 当其他线程执行 release 操作(比如 FutureTask.run()或 FutureTask.cancel(…)) 唤醒当前线程后,当前线程再次执行 tryAcquireShared()将返回正值 1,当前线程将离开线程等待队列并唤醒它的后继线程(这里会产生级联唤醒的效果)。

4) 最后返回计算的结果或抛出异常。


FutureTask.run()方法

执行过程如下。

1) 执行在构造函数中指定的任务(Callable.call())。

2) 以原子方式来更新同步状态(调用 AQS.compareAndSetState(int expect,int update),设置 state 为执行完成状态 RAN)。

如果这个原子操作成功,就设置代表计算结果的变量 result 的值为 Callable.call()的返回值,然后调用 AQS.releaseShared(int arg)。

3) AQS.releaseShared(int arg)首先会回调在子类 Sync 中实现的 tryReleaseShared (arg)来执行 release 操作(设置运行任务的线程 runner 为 null,然后返回 true),然后唤醒线程等待队列中的第一个线程。

4) 调用 FutureTask.done()。


当执行 FutureTask.get()方法时,如果 FutureTask 不是处于执行完成状态 RAN 或已取消状态 CANCELLED,当前执行线程将到 AQS 的线程等待队列中等待(见下图的线程 A、B、C 和 D)。

当某个线程执行 FutureTask.run()方法或 FutureTask.cancel(...)方法 时,会唤醒线程等待队列的第一个线程(见图 10-16 所示的线程 E 唤醒线程 A)。

 

FutureTask 的级联唤醒示意图

假设开始时 FutureTask 处于未启动状态或已启动状态,等待队列中已经有 3 个线程 (A、B 和 C)在等待。

此时,线程 D 执行 get()方法将导致线程 D 也到等待队列中去等待。

当线程 E 执行 run()方法时,会唤醒队列中的第一个线程 A。线程 A 被唤醒后,首先把自己从队列中删除,然后唤醒它的后继线程 B,最后线程 A 从 get()方法返回。

线程 B、C 和 D 重复 A 线程的处理流程。最终,在队列中等待的所有线程都被级联唤醒并从 get()方法返回。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值