java八股文面试[多线程]——FutureTask

FutureTask介绍

FutureTask是一个可以取消异步 任务的类。FutureTask对Future做的一个基本实现。可以调用方法去开始和取消一个任务。

一般是配合Callable去使用。

异步任务启动之后,可以获取一个绑定当前异步任务的FutureTask。

可以基于FutureTask的方法去取消任务,查看任务是否结果 ,以及获取任务的返回结果

FutureTask内部的整体结构中,实现了RunnableFuture 的接口,这个接口又继承了Runnable , Future 这个两个接口。所以FutureTask也可以作为任务直接交给线程池去处理。

FutureTask应用

大方向是FutureTask对任务的控制:

* 任务执行过程中状态的控制

* 任务执行完毕后,返回结果的获取

FutureTask的任务在执行run方法后,是无法被再次运行 ,需要使用runAndReset方法才可以。

FutureTask源码分析

看FutureTask的源码,要从几个方向去看:

* 先查看FutureTask中提供的一些状态

* 在查看任务的执行过程

FutureTask中的核心属性

清楚任务的流转状态是怎样的,其次对于核心属性要追到是干嘛的。

java 复制代码
/**
 FutureTask的核心属性
 FutureTask任务的状态流转
 * NEW -> COMPLETING -> NORMAL           任务正常执行,并且返回结果也正常返回
 * NEW -> COMPLETING -> EXCEPTIONAL      任务正常执行,但是结果是异常
 * NEW -> CANCELLED                      任务被取消   
 * NEW -> INTERRUPTING -> INTERRUPTED    任务被中断
 */
// 记录任务的状态
private volatile int state;
// 任务被构建之后的初始状态
private static final int NEW          = 0;
private static final int COMPLETING   = 1;
private static final int NORMAL       = 2;
private static final int EXCEPTIONAL  = 3;
private static final int CANCELLED    = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED  = 6;

/** 需要执行任务,会被赋值到这个属性 */
private Callable<V> callable;
/** 任务的任务结果要存储在这几个属性中 */
private Object outcome; // non-volatile, protected by state reads/writes
/** 执行任务的线程 */
private volatile Thread runner;
/** 等待返回结果的线程Node对象, */
private volatile WaitNode waiters;
static final class WaitNode {
    volatile Thread thread;
    volatile WaitNode next;
    WaitNode() { thread = Thread.currentThread(); }
}

FutureTask的run方法

任务执行前的一些判断,以及调用任务封装结果的方式,还有最后的一些后续处理

java 复制代码
// 当线程池执行FutureTask任务时,会调用的方法
public void run() {
    // 如果当前任务状态不是NEW,直接return告辞
    if (state != NEW ||  
        // 如果状态正确是NEW,这边需要基于CAS将runner属性设置为当前线程
        // 如果CAS失败,直接return告辞
        !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread()))
        return;

    try {
        // 将要执行的任务拿到
        Callable<V> c = callable;
        // 健壮性判断,保证任务不是null
        // 再次判断任务的状态是NEW(DCL)
        if (c != null && state == NEW) {
            // 执行任务
            // result:任务的返回结果
            // ran:如果为true,任务正常结束。 如果为false,任务异常结束。
            V result;
            boolean ran;
            try {
                // 执行任务
                result = c.call();
                // 正常结果,ran设置为true
                ran = true;
            } catch (Throwable ex) {
                // 如果任务执行期间出了异常
                // 返回结果置位null
                result = null;
                // ran设置为false
                ran = false;
                // 封装异常结果
                setException(ex);
            }
            if (ran)
                // 封装正常结果
                set(result);
        }
    } finally {
        // 将执行任务的线程置位null
        runner = null;
        // 拿到任务的状态
        int s = state;
        // 如果状态大于等于INTERRUPTING
        if (s >= INTERRUPTING)
            // 进来代表任务中断,做一些后续处理
            handlePossibleCancellationInterrupt(s);
    }
}

当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交给Executor 执行;也可以通ExecutorService .submit (...)方法返回一个 FutureTask,然后执行FutureTask.get()方法或FutureTask.cancel(...)方法。除此以外,还可以单独使用FutureTask。

当一个线程需要等待另一个线程 把某个任务执行完后它才能继续执行,此时可以使用FutureTask。假设有多个线程执行若干任务,每个任务最多只能被执行一次 。当多个线程试图同时执行同一个任务时,只允许一个线程执行任务 ,其他线程需要等待这个任务执行完后才能继续执行。下面是对应的示例代码。

java 复制代码
private final ConcurrentMap<Object, Future<String>> taskCache = new ConcurrentHashMap<>();

    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 = () -> taskName;
                FutureTask<String> futureTask = new FutureTask<>(task);
                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实现

jdk1.8的FutureTask有个说明:

修订说明:这与这个类以前依赖AbstractQueuedsynchronizer 的版本不同,主要是为了避免在取消竞争期间保留中断状态 让用户感到意外。在当前的设计中,Sync控件依赖于通过CAS 更新的"state"字段来跟踪完成,以及一个简单的Treiber堆栈来保存等待的线程。

风格注意:与往常一样,我们绕过了使用 AtomicXFieldUpdaters的开销 ,而是直接使用Unsafe

Future

FutureTask实现了Future接口,Future接口有5个方法

1、boolean cancel (boolean mayInterruptIfRunning)

尝试取消当前任务的执行。如果任务已经取消、已经完成或者其他原因不能取消,尝试将失败。如果任务还没有启动就调用了cancel(true),任务将永远不会被执行。如果任务已经启动,参数mayInterruptIfRunning 将决定任务是否应该中断执行该任务的线程,以尝试中断该任务。

如果任务不能被取消,通常是因为它已经正常完成,此时返回false,否则返回true

2、boolean isCancelled ()

如果任务在正常结束之前被被取消返回true

3、boolean isDone ()

正常结束、异常或者被取消导致任务完成,将返回true

4、V get ()

等待任务结束,然后获取结果,如果任务在等待过程中被终端将抛出InterruptedException,如果任务被取消将抛出CancellationException,如果任务中执行过程中发生异常将抛出ExecutionException。

5、V get (long timeout, TimeUnit unit)

任务最多在给定时间内完成并返回结果,如果没有在给定时间内完成任务将抛出TimeoutException。

FutureTask状态转换

FutureTask有以下7中状态:

FutureTask任务的运行状态,最初为NEW。运行状态仅在setsetExceptioncancel 方法中转换为终端状态 。在完成过程中,状态可能呈现出瞬时值 INTERRUPTING(仅在中断运行程序以满足cancel(true) 的情况下)或者COMPLETING (在设置结果时)状态时。从这些中间状态到最终状态的转换使用成本更低有序/延迟写 ,因为值是统一的,需要进一步修改。

state:表示当前任务的运行状态,FutureTask的所有方法都是围绕state开展的 ,state声明为volatile,保证了state的可见性,当对state进行修改时所有的线程都会看到。

NEW:表示一个新的任务,初始状态

COMPLETING:当任务被设置结果时,处于COMPLETING状态,这是一个中间状态

NORMAL:表示任务正常结束。

EXCEPTIONAL:表示任务因异常而结束

CANCELLED:任务还未执行之前就调用了cancel(true)方法,任务处于CANCELLED

INTERRUPTING:当任务调用cancel(true)中断程序时,任务处于INTERRUPTING状态,这是一个中间状态

INTERRUPTED:任务调用cancel(true)中断程序时会调用interrupt()方法中断线程运行,任务状态由INTERRUPTING转变为INTERRUPTED

可能的状态过渡

1、NEW -> COMPLETING -> NORMAL:正常结束

2、NEW -> COMPLETING -> EXCEPTIONAL:异常结束

3、NEW -> CANCELLED:任务被取消

4、NEW -> INTERRUPTING -> INTERRUPTED:任务出现中断

知识来源:

FutureTask详解_索码理的博客-CSDN博客

相关推荐
WG_1737 分钟前
C++多态
开发语言·c++·面试
鱼跃鹰飞1 小时前
Leetcode面试经典150题-130.被围绕的区域
java·算法·leetcode·面试·职场和发展·深度优先
鱼跃鹰飞11 小时前
Leetcode面试经典150题-349.两个数组的交集
算法·leetcode·面试
戊子仲秋15 小时前
【LeetCode】每日一题 2024_9_19 最长的字母序连续子字符串的长度(字符串,双指针)
算法·leetcode·职场和发展
哲伦贼稳妥17 小时前
程序人生-我的外服经历(4)
经验分享·程序人生·职场和发展
程序猿进阶17 小时前
如何在 Visual Studio Code 中反编译具有正确行号的 Java 类?
java·ide·vscode·算法·面试·职场和发展·架构
无名之逆18 小时前
云原生(Cloud Native)
开发语言·c++·算法·云原生·面试·职场和发展·大学期末
andrew_12191 天前
腾讯 IEG 游戏前沿技术 一面复盘
java·redis·sql·面试
andrew_12191 天前
腾讯 IEG 游戏前沿技术 二面复盘
后端·sql·面试
寻求出路的程序媛1 天前
JVM —— 类加载器的分类,双亲委派机制
java·jvm·面试