Java多线程9—Future类框架

FutureTask是Java并发编程中用于获取异步任务结果的工具类,它实现了Future接口,允许获取Callable任务的执行结果。文章详细剖析了FutureTask的内部机制,包括核心属性(如state、outcome、runner、waiters)、构造函数、CAS操作、接口实现(如Runnable接口、Future接口的实现)以及cancel、isCancelled、isDone、get等方法的原理。此外,还对比了FutureTask与CompletableFuture的区别,指出FutureTask在获取结果时可能导致阻塞,而CompletableFuture提供了全异步解决方案。
摘要由CSDN通过智能技术生成


此文大部分内容并不是本人执笔完成,因拷贝到本地笔记时忘标记原链接,因此无法指明原作者,罪过罪过,还望海涵,特此声明!

1. Future框架

前面提到,在继承Thread类或实现Runnable接口时,并没有返回值,也就无法获取任务执行的结果。因此JDK1.5提供了Callable接口:

public interface Callable<V> {
   
    	
    V call() throws Exception;
}

不过,Callable接口需要搭配Future类来使用,也就是说,Future可以拿到异步执行任务的返回值。

1.1 Future接口

Future框架一般搭配线程池使用,当然可以用单独使用,其提供了异步条件下的一般方法。

package java.util.concurrent;

public interface Future<V> {
   
    boolean cancel(boolean mayInterruptIfRunning);

    boolean isCancelled();

    boolean isDone();

    V get() throws InterruptedException, ExecutionException;

    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

1.2 FutureTask类

FutureTask类是Future接口的实现,其还实现了Runnable接口,因此其不光可以作为简单的Runnable对象来使用,还可以在Runnable对象执行过程中对其进行控制。

关于Java并发工具类的三板斧,我们在分析AQS源码的时候已经说过了,即:

状态,存储结构,CAS操作

首先来找状态,在FutureTask中,状态是由state属性来表示的(volatile类型的,确保了不同线程对它修改的可见性)

线程状态:

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;

状态转换图:

存储结果:

接着我们来看队列,在FutureTask中,队列的实现是一个单向链表,它表示所有等待任务执行完毕的线程的集合。我们知道,FutureTask实现了Future接口,可以获取“Task”的执行结果,那么如果获取结果时,任务还没有执行完毕怎么办呢?那么获取结果的线程就会在一个等待队列中挂起,直到任务执行完毕被唤醒。

我们前面说过,在并发编程中使用队列通常是将当前线程包装成某种类型的数据结构扔到等待队列中,我们先来看看队列中的每一个节点是怎么个结构:

static final class WaitNode {
   
    volatile Thread thread;
    volatile WaitNode next;
    WaitNode() {
    thread = Thread.currentThread(); }
}

值得一提的是,FutureTask中的这个单向链表是当做来使用的,确切来说是当做Treiber栈来使用的,不了解Treiber栈是个啥的可以简单的把它当做是一个线程安全的栈,它使用CAS来完成入栈出栈操作

为啥要使用一个线程安全的栈呢,因为同一时刻可能有多个线程都在获取任务的执行结果,如果任务还在执行过程中,则这些线程就要被包装成WaitNode扔到Treiber栈的栈顶,即完成入栈操作,这样就有可能出现多个线程同时入栈的情况,因此需要使用CAS操作保证入栈的线程安全,对于出栈的情况也是同理。

由于FutureTask中的队列本质上是一个Treiber栈,那么使用这个队列就只需要一个指向栈顶节点的指针就行了,在FutureTask中,就是waiters属性:

/** Treiber stack of waiting threads */
private volatile WaitNode waiters;

CAS操作

CAS操作大多数是用来改变状态的,在FutureTask中也不例外。我们一般在静态代码块中初始化需要CAS操作的属性的偏移量:

    // Unsafe mechanics
    private static final sun.misc.Unsafe UNSAFE;
    private static final long stateOffset;
    private static final long runnerOffset;
    private static final long waitersOffset;
    static {
   
        try {
   
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> k = FutureTask.class;
            stateOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("state"));
            runnerOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("runner"));
            waitersOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("waiters"));
        } catch (Exception e) {
   
            throw new Error(e);
        }
    }

从这个静态代码块中我们也可以看出,CAS操作主要针对3个属性,包括staterunnerwaiters,说明这3个属性基本是会被多个线程同时访问的。其中state属性代表了任务的状态,waiters属性代表了指向栈顶节点的指针,这两个我们上面已经分析过了。runner属性代表了执行FutureTask中的“Task”的线程。为什么需要一个属性来记录执行任务的线程呢?这是为了中断或者取消任务做准备的,只有知道了执行任务的线程是谁,我们才能去中断它。

定义完属性的偏移量之后,接下来就是CAS操作本身了。在FutureTask,CAS操作最终调用的还是Unsafe类的compareAndSwapXXX方法,关于这一点,我们上一篇预备知识中已经讲过了,这里不再赘述。

核心属性

前面我们以java并发编程工具类的“三板斧”为切入点分析了FutureTask的状态,队列和CAS操作,对这个工具类有了初步的认识。接下来,我们就要开始进入源码分析了。首先我们先来看看FutureTask的几个核心属性:

    /**
     * The run state of this task, initially NEW.  The run state
     * transitions to a terminal state only in methods set,
     * setException, and cancel.  During completion, state may take on
     * transient values of COMPLETING (while outcome is being set) or
     * INTERRUPTING (only while interrupting the runner to satisfy a
     * cancel(true)). Transitions from these intermediate to final
     * states use cheaper ordered/lazy writes because values are unique
     * and cannot be further modified.
     *
     * Possible state transitions:
     * 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;

    /** The underlying callable; nulled out after running */
    private Callable<V> callable;
    /** The result to return or exception to throw from get() */
    private Object outcome; // non-volatile, protected by state reads/writes
    /** The thread running the callable; CASed during run() */
    private volatile Thread runner;
    /** Treiber stack of waiting threads */
    private volatile WaitNode waiters;

可以看出,FutureTask的核心属性只有5个:

  • state
  • callable
  • outcome
  • runner
  • waiters

关于 state waiters runner三个属性我们上面已经解释过了。剩下的callable属性代表了要执行的任务本身,即FutureTask中的“Task”部分,为Callable类型,这里之所以用Callable而不用Runnable是因为FutureTask实现了Future接口,需要获取任务的执行结果。outcome属性代表了任务的执行结果或者抛出的异常,为Object类型,也就是说outcome可以是任意类型的对象,所以当我们将正常的执行结果返回给调用者时,需要进行强制类型转换,返回由Callable定义的V类型。这5个属性综合起来就完成了整个FutureTask的工作,使用关系如下:

  • 任务本尊:callable
  • 任务的执行者:runner
  • 任务的结果:outcome
  • 获取任务的结果:state + outcome + waiters
  • 中断或者取消任务:state + runner + waiters

构造函数

介绍完核心属性之后,我们来看看FutureTask的构造函数:

public FutureTask(Callable<V> callable) {
   
    if (callable == null)
        throw new NullPointerException();
    this.callable = callable;
    this.state = NEW;       // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
   
    this.callable = Executors.callable(runnable, result);
    this.state = NEW;       // ensure visibility of callable
}

FutureTask共有2个构造函数,这2个构造函数一个是直接传入Callable对象, 一个是传入一个Runnable对象和一个指定的result, 然后通过Executors工具类将它适配成callable对象, 所以这两个构造函数的本质是一样的:

  1. 用传入的参数初始化callable成员变量
  2. 将FutureTask的状态设为NEW

(关于将Runnable对象适配成Callable对象的方法Executors.callable(runnable, result)我们在上一篇预备知识中已经讲过了,不记得的同学可以倒回去再看一下)

接口实现

上一篇我们提过,FutureTask实现了RunnableFuture接口:

public class FutureTask<V> implements RunnableFuture<V> {
   
    ...
}

因此,它必须实现Runnable和Future接口的所有方法。

Runnable接口实现

要实现Runnable接口, 就得覆写run方法, 我们看看FutureTask的run方法干了点啥:

public void run() {
   
    if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread</
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值