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博客

相关推荐
前端双越老师41 分钟前
前端面试常见的 10 个场景题
前端·面试·求职
Lee川17 小时前
优雅进化的JavaScript:从ES6+新特性看现代前端开发范式
javascript·面试
Lee川20 小时前
从异步迷雾到优雅流程:JavaScript异步编程与内存管理的现代化之旅
javascript·面试
晴殇i1 天前
揭秘JavaScript中那些“不冒泡”的DOM事件
前端·javascript·面试
绝无仅有1 天前
Redis过期删除与内存淘汰策略详解
后端·面试·架构
绝无仅有1 天前
Redis大Key问题排查与解决方案全解析
后端·面试·架构
AAA梅狸猫1 天前
Looper.loop() 循环机制
面试
AAA梅狸猫1 天前
Handler基本概念
面试
Wect1 天前
浏览器缓存机制
前端·面试·浏览器
掘金安东尼1 天前
Fun with TypeScript Generics:玩转 TS 泛型
前端·javascript·面试