FutureTask的使用与原理解析

Java 多线程编程简介与线程的创建一篇我们介绍了线程的创建方式,分别为继承Thread、实现Runnable接口和实现Callable接口,本篇我们介绍线程的另一种创建方式FutureTask。相对于Runnable的实现方式不能有返回结果,FutureTask对其进行了适配,使其可以返回一个默认的结果,并且支持Callable方式线程的创建。如下我们先使用一个例子介绍FutureTask的使用:

private static void futureTaskRunnable() throws InterruptedException, ExecutionException {
    //构造方法为一个接口Runnable实现和一个默认的返回值
    FutureTask<String> future = new FutureTask<String>(()->{
    }, "Hello,Word");
    //调用run()方法运行线程
    future.run();
    System.out.println("futureTaskRunnable: " + future.get());
}
	
private static void futureTaskcallable() throws InterruptedException, ExecutionException {
    //构造方法为接口 Callable的实现
    FutureTask<String> future = new FutureTask<String>(()->{
        return "test";
    });
    //调用run()方法运行线程
    future.run();
    System.out.println("futureTaskcallable: " + future.get());
}

如上代码,我们分别使用Runnable和Callable方式创建了一个FutureTask,并且可以调用FutureTask的run()方法启动线程,如下根据FutureTask的类层次结构图,FutureTask除了实现Future接口外,还实现了Runnable接口。因此,FutureTask可以交给 Executor执行。

 前面我们说FutureTask对Runnable进行了适配,使其可以返回一个默认值,下面我们就查看FutureTask是如何适配Runnable接口的,其实这是一个适配器模式,如下为FutureTask适配Runnable接口的实现。

 public FutureTask(Runnable runnable, V result) {
    //将Runnable接口封装成Callable接口,方法如下
    this.callable = Executors.callable(runnable, result);
    this.state = NEW; 
 }

public static <T> Callable<T> callable(Runnable task, T result) {
    if (task == null)
       throw new NullPointerException();
    //适配器模式,下面我们看RunnableAdapter
    return new RunnableAdapter<T>(task, result);
}
//适配类
static final class RunnableAdapter<T> implements Callable<T> {
    final Runnable task;
    final T result;
    RunnableAdapter(Runnable task, T result) {
        this.task = task;
        this.result = result;
    }
    public T call() {
        task.run();
        return result;
    }
}

介绍完FutureTask的使用之后,我们接着介绍FutureTask的实现原理,在JDK 1.6中Future用到了AbstractQueuedSynchronizer,在JUC包中很多与并发相关的工具类都用到了AQS类,可以说它是并发工具的基石,如果对该类不了解的可以参考AQS 队列同步器 AbstractQueuedSynchronizer 的简单使用。AQS是一个同步框架,它提供通用机制来原子性管理同步状态、阻塞和唤醒线程,以及维护被阻塞线程的队列。每一个基于AQS实现的同步器都会包含两种类型的操作,

  • 至少一个acquire操作。这个操作阻塞调用线程,除非直到AQS的状态允许这个线程继续执行。FutureTask的acquire操作为get()/get(long timeout,TimeUnit unit)方法调用。
  • 至少一个release操作。这个操作改变AQS的状态,改变后的状态可允许一个或多个阻塞 线程被解除阻塞。FutureTask的release操作包括run()方法和cancel(…)方法

 其具体实现这里不再介绍AQS的使用方式基本类似,在这篇博客中,我们主要介绍Future在Task中的实现,相当于使用AQS的复杂度,FutureTask在JDK1.8的实现更加简单明了,让我们从get()方法看起,源码如下:

public V get() throws InterruptedException, ExecutionException {
    //获取当前FutureTask的状态
    int s = state;
    //如果未完成,加入到等待队列
    if (s <= COMPLETING)
       //加入到等待队列,等待线程执行完毕
       s = awaitDone(false, 0L);
    return report(s);
}

在get()方法中,如果任务未执行完毕,会被加入等待队列等待任务执行完毕,这里我们看下FutureTask中的等待队列与AQS中有何不同,相对于AQS中的等待队列,FutureTask中的更加简单,其定义如下:

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

下面我们看FutureTask是如何将当前线程加入到等待队列等待另外一个线程结束后返回才继续执行的,其主要逻辑在awaitDone()方法中,get()和get(long timeout,TimeUnit unit)最终都调用的该方法,其源码如下所示:

//参数time 是否超时,nanos为毫秒数
private int awaitDone(boolean timed, long nanos) throws InterruptedException {
    //如果设置了超时,时间相加,否则为0
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    WaitNode q = null;
    boolean queued = false;
    for (;;) {
        //如果线程中断,,从队列移除线程,抛出异常
        if (Thread.interrupted()) {
            removeWaiter(q);
            throw new InterruptedException();
        }
        int s = state;
        //如果已经执行完毕,直接返回
        if (s > COMPLETING) {
            if (q != null)
                q.thread = null;
            return s;
        }
        else if (s == COMPLETING) // 任务正在执行,当前线程变为Ready状态,
            Thread.yield();
        else if (q == null)
            q = new WaitNode(); //创建一个WaitINGNode
        else if (!queued)
            CAS设置值
            queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q);
        //有超时时间的处理
        else if (timed) {
            nanos = deadline - System.nanoTime();
            if (nanos <= 0L) {
                    removeWaiter(q);
                    return state;
            }
            //调用LockSupport
            LockSupport.parkNanos(this, nanos);
        }
        else
            //调用LockSupport
            LockSupport.park(this);
    }
}

上面代码就是将线程加入到等待队列的过程,线程在任务执行完成之后,才会返回继续执行当前线程,下面我们介绍任务执行完毕或者取消之后的的逻辑,其主要在run()方法和cancel方法中,其最终调用了finishCompletion()方法,代码如下:

private void finishCompletion() {
    // assert state > COMPLETING;
    for (WaitNode q; (q = waiters) != null;) {
    if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
        for (;;) {
            Thread t = q.thread;
            if (t != null) {
                q.thread = null;
                //调用unPart唤醒线程
                LockSupport.unpark(t);
            }
            WaitNode next = q.next;
            if (next == null)
                break;
                q.next = null; // unlink to help gc
                q = next;
            }
            break;
            }
    }
    done();
    callable = null;        // to reduce footprint
}

猜你喜欢

转载自blog.csdn.net/wk19920726/article/details/108724479