线程池源码分析-FutureTask

1 系列目录

该系列打算从一个最简单的Executor执行器开始一步一步扩展到ThreadPoolExecutor,希望能粗略的描述出线程池的各个实现细节。针对JDK1.7中的线程池

2 Executor接口说明

Executor执行器,就是执行一个Runnable任务,可同步可异步,接口定义如下:

public interface Executor {

    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the <tt>Executor</tt> implementation.
     *
     * @param command the runnable task
     * @throws RejectedExecutionException if this task cannot be
     * accepted for execution.
     * @throws NullPointerException if command is null
     */
    void execute(Runnable command);
}

ExecutorService则继承了Executor,描述了线程池应该具有的功能特性,来详细看下接口,这些接口都有详细的文档可以阅读,这里就不再列出来了,目前只说明我们重点关注的接口。

<T> Future<T> submit(Callable<T> task);

可以提交一个Callable,并且返回一个Future用于追踪提交的任务。如何追踪一个任务的状态和返回数据呢?那就需要将提交的任务进行封装,对任务的执行、执行过程中的异常、中断、返回结果进行统一的监控处理。下面就来看看AbstractExecutorService对上述submit的实现

public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}

protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
    return new FutureTask<T>(callable);
}

从上面看到就是对Callable封装成一个新的任务,即FutureTask,调用Executor的原始接口execute方法来执行FutureTask,并且返回给用户FutureTask对象,用于追踪任务的状态和数据,下面就需要我们来详细看看FutureTask如何对任务进行封装的

3 FutureTask的实现细节

3.1 FutureTask的属性和构造函数

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;

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

有一个状态变量state,一个Callable callable即原始任务,Object outcome存放原始任务的输出结果或者异常,Thread runner运行该任务的线程,WaitNode waiters等待获取任务结果的等待者

3.2 FutureTask的get方法实现

使用FutureTask阻塞式等待任务执行结果,一种是永远阻塞另一种就是阻塞一定时间否则报超时异常,如下2个方法

public V get() throws InterruptedException, ExecutionException {
    int s = state;
    if (s <= COMPLETING)
        s = awaitDone(false, 0L);
    return report(s);
}

public V get(long timeout, TimeUnit unit)
    throws InterruptedException, ExecutionException, TimeoutException {
    if (unit == null)
        throw new NullPointerException();
    int s = state;
    if (s <= COMPLETING &&
        (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
        throw new TimeoutException();
    return report(s);
}

阻塞式等待的核心逻辑就在上述awaitDone方法中,来详细看看

private int awaitDone(boolean timed, long nanos)
    throws InterruptedException {
    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) // cannot time out yet
            Thread.yield();
        else if (q == null)
            q = new WaitNode();
        else if (!queued)
            queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                 q.next = waiters, q);
        else if (timed) {
            nanos = deadline - System.nanoTime();
            if (nanos <= 0L) {
                removeWaiter(q);
                return state;
            }
            LockSupport.parkNanos(this, nanos);
        }
        else
            LockSupport.park(this);
    }
}

可以看到有一个for循环不断处理着各种情况:

1 从最开始的WaitNode q = null,构建了一个WaitNode,即代表着当前线程作为一个等待者,WaitNode就是一个简单的链表,如下

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

2 构建好WaitNode之后就要将该WaitNode放入链表中,这时候就会涉及多线程问题,使用UNSAFE的CAS来解决,这种方式也是AtomicLong等众多原子类的底层实现方式

3 成功放入WaitNode链表之后,采用LockSupport的park阻塞当前线程,要么只阻塞一定时间要么一直阻塞,直到被LockSupport的unpark唤醒。LockSupport在锁的底层实现AQS中也非常常见,使用了LockSupport就可以不用在for循环里不断判断当前任务状态而浪费CPU,只需要当前任务完成之后,使用LockSupport对等待线程进行unpark,就可以使等待的线程退出等待继续往下执行

4 如果LockSupport阻塞时间到了,还未收到unpark,则需要从等待者链表中删除当前线程代表的等待者

3.3 FutureTask的任务执行过程

public void run() {
    if (state != NEW ||
        !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                     null, Thread.currentThread()))
        return;
    try {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
                set(result);
        }
    } finally {
        // runner must be non-null until state is settled to
        // prevent concurrent calls to run()
        runner = null;
        // state must be re-read after nulling runner to prevent
        // leaked interrupts
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

1 一旦FutureTask任务开始执行了,就需要将当前执行线程设置到FutureTask的volatile Thread runner属性中

2 执行原始任务Callable的call方法,可能成功也可能失败也可能被中断被取消

文档中有如下状态的迁移过程:

Possible state transitions:
 * NEW -> COMPLETING -> NORMAL
 * NEW -> COMPLETING -> EXCEPTIONAL
 * NEW -> CANCELLED
 * NEW -> INTERRUPTING -> INTERRUPTED

来看下成功和失败方法

protected void set(V v) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = v;
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
        finishCompletion();
    }
}

protected void setException(Throwable t) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = t;
        UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
        finishCompletion();
    }
}

都是首先将状态变成COMPLETING正在结束中,然后设置outcome,成功则设置正常的返回值,失败则设置成异常,然后根据划定最终的状态结果,成功就是NORMAL,失败就是EXCEPTIONAL,最后呢调用finishCompletion,去unpark之前说的WaitNode中对应的线程们

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;
                    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
}

这里就是遍历WaitNode链表,对每一个WaitNode对应的线程依次进行LockSupport.unpark(t),使其结束阻塞。WaitNode通知完毕后,调用一个done方法,目前该方法是空的实现,所以你如果想在任务完成后执行一些动作的时候就可以重写该方法

有一个问题就是:为什么一定要加入COMPLETING状态呢?能不能直接过度到NORMAL或者EXCEPTIONAL?

目前我的理解是:NORMAL或者EXCEPTIONAL是一种最终状态,所以在出现该状态前,outcome必须已经被设置了,即有如下代码:

protected void set(V v) {
    outcome = v;
    UNSAFE.compareAndSwapInt(this, stateOffset, NEW, NORMAL)
    finishCompletion();
}

但是因为存在外部直接取消该任务,所以结果状态的设置和outcome必须是同步的,且outcome在前,为了保证代码的同步可以使用锁

protected void set(V v) {
    synchronized(){
        outcome = v;
        UNSAFE.compareAndSwapInt(this, stateOffset, NEW, NORMAL)
        finishCompletion();
    }
}

为了减少锁带来的开支,就可以引入一个中间状态COMPLETING,通过CAS来间接实现锁的竞争,同时又保证outcome在最终状态NORMAL或者EXCEPTIONAL之前被设置

3.4 FutureTask任务的取消

public boolean cancel(boolean mayInterruptIfRunning) {
    if (state != NEW)
        return false;
    if (mayInterruptIfRunning) {
        if (!UNSAFE.compareAndSwapInt(this, stateOffset, NEW, INTERRUPTING))
            return false;
        Thread t = runner;
        if (t != null)
            t.interrupt();
        UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED); // final state
    }
    else if (!UNSAFE.compareAndSwapInt(this, stateOffset, NEW, CANCELLED))
        return false;
    finishCompletion();
    return true;
}

取消任务,有2种情况,一种该任务正在运行,一种就是非运行状态,所以需要用户给出明示是否中断正在运行的任务,即需要一个参数mayInterruptIfRunning

中断任务就是通过中断运行该任务的线程,即直接调用该线程的interrupt()方法

4 结束语

FutureTask大部分就简单分析完了,其他的自己去看下就行了。至此我们了解了一个任务被提交经过了封装,变成了一个新的任务FutureTask,同时FutureTask也明确了该任务的整个执行过程,只留出核心execute(futureTask)方法需要被子类来实现,下一篇文章就重点介绍下ThreadPoolExecutor对该核心方法的实现

时间: 2024-10-29 19:54:40

线程池源码分析-FutureTask的相关文章

线程池源码分析-ThreadPoolExecutor

1 系列目录 线程池接口分析以及FutureTask设计实现 线程池源码分析-ThreadPoolExecutor 该系列打算从一个最简单的Executor执行器开始一步一步扩展到ThreadPoolExecutor,希望能粗略的描述出线程池的各个实现细节.针对JDK1.7中的线程池 2 ThreadPoolExecutor 从上一篇文章中了解到:核心execute(futureTask)方法需要被子类来实现,所以我们就俩重点看看ThreadPoolExecutor是如何实现这个核心方法的 2.

nginx线程池源码分析_nginx

周末看了nginx线程池部分的代码,顺手照抄了一遍,写成了自己的版本.实现上某些地方还是有差异的,不过基本结构全部摘抄. 在这里分享一下.如果你看懂了我的版本,也就证明你看懂了nginx的线程池. 本文只列出了关键数据结构和API,重在理解nginx线程池设计思路.完整代码在最后的链接里. 1.任务节点 typedef void (*CB_FUN)(void *); //任务结构体 typedef struct task { void *argv; //任务函数的参数(任务执行结束前,要保证参数

tomcat的NIO线程模型源码分析

1 tomcat8的并发参数控制 这种问题其实到官方文档上查看一番就可以知道,tomcat很早的版本还是使用的BIO,之后就支持NIO了,具体版本我也不记得了,有兴趣的自己可以去查下.本篇的tomcat版本是tomcat8.5.可以到这里看下tomcat8.5的配置参数 我们先来简单回顾下目前一般的NIO服务器端的大致实现,借鉴infoq上的一篇文章Netty系列之Netty线程模型中的一张图 一个或多个Acceptor线程,每个线程都有自己的Selector,Acceptor只负责accept

OkHttp 3.7源码分析(五)——连接池

OkHttp3.7源码分析文章列表如下: OkHttp源码分析--整体架构 OkHttp源码分析--拦截器 OkHttp源码分析--任务队列 OkHttp源码分析--缓存策略 OkHttp源码分析--多路复用 接下来讲下OkHttp的连接池管理,这也是OkHttp的核心部分.通过维护连接池,最大限度重用现有连接,减少网络连接的创建开销,以此提升网络请求效率. 1. 背景 1.1 keep-alive机制 在HTTP1.0中HTTP的请求流程如下: 这种方法的好处是简单,各个请求互不干扰.但在复杂

JVM源码分析之一个Java进程究竟能创建多少线程

概述 虽然这篇文章的标题打着JVM源码分析的旗号,不过本文不仅仅从JVM源码角度来分析,更多的来自于Linux Kernel的源码分析,今天要说的是JVM里比较常见的一个问题 这个问题可能有几种表述 一个Java进程到底能创建多少线程? 到底有哪些因素决定了能创建多少线程? java.lang.OutOfMemoryError: unable to create new native thread的异常究竟是怎么回事 不过我这里先声明下可能不能完全百分百将各种因素都理出来,因为毕竟我不是做Lin

HDFS源码分析数据块复制监控线程ReplicationMonitor(一)

        ReplicationMonitor是HDFS中关于数据块复制的监控线程,它的主要作用就是计算DataNode工作,并将复制请求超时的块重新加入到待调度队列.其定义及作为线程核心的run()方法如下: /** * Periodically calls computeReplicationWork(). * 周期性调用computeReplicationWork()方法 */ private class ReplicationMonitor implements Runnable

HDFS源码分析心跳汇报之BPServiceActor工作线程运行流程

        在<HDFS源码分析心跳汇报之数据结构初始化>一文中,我们了解到HDFS心跳相关的BlockPoolManager.BPOfferService.BPServiceActor三者之间的关系,并且知道最终HDFS的心跳是通过BPServiceActor线程实现的.那么,这个BPServiceActor线程到底是如何工作的呢?本文,我们将继续HDFS心跳分析之BPServiceActor工作线程运行流程.         首先,我们先看下         那么,BPServiceA

Android AsyncTask源码分析_Android

Android中只能在主线程中进行UI操作,如果是其它子线程,需要借助异步消息处理机制Handler.除此之外,还有个非常方便的AsyncTask类,这个类内部封装了Handler和线程池.本文先简要介绍AsyncTask的用法,然后分析具体实现. 基本用法AsyncTask是一个抽象类,我们需要创建子类去继承它,并且重写一些方法.AsyncTask接受三个泛型参数: Params: 指定传给任务执行时的参数的类型 Progress: 指定后台任务执行时将任务进度返回给UI线程的参数类型 Res

我的Android进阶之旅------&amp;gt;Android中AsyncTask源码分析

在我的<我的Android进阶之旅------>android异步加载图片显示,并且对图片进行缓存实例>文章中,先后使用了Handler和AsyncTask两种方式实现异步任务机制. 下面先来看一段代码,这段代码是用来显示条目时候调用的方法. @Override public View getView(int position, View convertView, ViewGroup parent) { ImageView imageView = null; TextView textV