FutureTask 源码解析

Future接口和实现Future接口的FutureTask类,代表异步计算的结果。FutureTask除了实现Future接口外,还实现了Runnable接口。因此,FutureTask可以交给 Executor执行,也可以由调用线程直接执行(FutureTask.run())。

类图

FutureTask.png

核心属性

/**
 *
 * 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;

// 异步任务
private Callable<V> callable;
// 异步任务的返回值
private Object outcome; // non-volatile, protected by state reads/writes

可能发生的任务状态转换:

  1. NEW -> COMPLETING -> NORMAL
  2. NEW -> COMPLETING -> EXCEPTIONAL
  3. NEW -> CANCELLED
  4. NEW -> INTERRUPTING -> INTERRUPTED

NORMAL、EXCEPTIONAL、CANCELLED和INTERRUPTED都是最终状态,表示任务已结束。

构造函数

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可以接受一个Callable或是Runnable。如果是Runnable需要我们传一个返回值进去。

run()

public void run() {
    // 判断状态
    if (state != NEW ||
        // CAS 设置执行任务的线程(相当于加锁)
        !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();
                // 正常结束,设置标记为true
                ran = true;
            } catch (Throwable ex) {
                result = null;
                // 异常结束,设置标记为false
                ran = false;
                // 异常处理,唤醒get方法阻塞线程
                setException(ex);
            }
            if (ran)
                // 保存结果,唤醒get方法阻塞线程
                set(result);
        }
    } finally {
        // 相当于解锁
        runner = null;
        // 重新检查状态,判断是否需要响应中断
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}
  1. 先判断任务状态,并加锁,防止任务被重复执行
  2. 再次检查一下任务状态,因为在第一步检查完了的时候,任务状态有可能已经发生了变化
  3. 执行任务
  4. 如果异常则在catch里面保存异常,唤醒get()方法阻塞线程
  5. 根据执行标记位,保存结果,唤醒get()方法阻塞线程
  6. 设置任务状态标记位
  7. 解锁,并判断是否响应中断

cancel()

public boolean cancel(boolean mayInterruptIfRunning) {
    // 判断任务状态,更新任务状态(相当于加锁)
    if (!(state == NEW &&   
          UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
              mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
        return false;
    try {    // in case call to interrupt throws exception
        if (mayInterruptIfRunning) {
            try {
                Thread t = runner;
                if (t != null)
                    // 中断线程
                    t.interrupt();
            } finally { // final state
                // 设置认为状态是已中断
                UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
            }
        }
    } finally {
        // 唤醒被get方法阻塞的线程
        finishCompletion();
    }
    return true;
}

尝试取消执行此任务。

  • 当FutureTask处于未启动状态时,执行FutureTask.cancel(...)方法将导致此任务永远不会被执 行;
  • 当FutureTask处于已启动状态时,执行FutureTask.cancel(true)方法将以中断执行此任务线程 的方式来试图停止任务;
  • 当FutureTask处于已启动状态时,执行FutureTask.cancel(false)方法将不会对正在执行此任务的线程产生影响(让正在执行的任务运行完成);
  • 当FutureTask处于已完 成状态时,执行FutureTask.cancel(…)方法将返回false。

get()

public V get() throws InterruptedException, ExecutionException {
    int s = state;
    // 如果任务还未完成,那执行等待
    if (s <= COMPLETING)
        s = awaitDone(false, 0L);
    // 返回结果
    return report(s);
}

get方法比较简单,直接调用了两个方法。一个是执行等待awaitDone,一个是返回结果report

  • 当FutureTask处于未启动或已启动状态时,执行FutureTask.get()方法将导致调用线程阻塞;
  • 当FutureTask处于已完成状态时,执行FutureTask.get()方法将导致调用线程立即返回结果或抛 出异常。

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;
        }
        // 判断任务是否是完成状态,如果是让出CPU执行权,等待任务最终结束
        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);
    }
}
  1. 计算出超时时间
  2. 响应中断
  3. 判断任务是否结束,如果是直接返回任务状态
  4. 判断任务是否完成,如果是调用yield方法让出CPU执行权,等待任务最终结束
  5. 阻塞线程
  6. 循环第2步

report()

private V report(int s) throws ExecutionException {
    Object x = outcome;
    if (s == NORMAL)
        return (V)x;
    if (s >= CANCELLED)
        throw new CancellationException();
    throw new ExecutionException((Throwable)x);
}

封装结果:如果正常结束就返回任务执行结果;如果是取消任务就抛出CancellationException异常;如果是异常结束就抛出任务执行遇到的异常。

get方法和cancel方法的执行示意图

get方法和cancel方法的执行示意图.jpg

总结

FutureTask的等待和唤醒使用的是LockSupport.parkLockSupport.unpark(t)方法。

参考

《java并发编程的艺术》

源码

https://github.com/wyh-spring-ecosystem-student/spring-boot-student/tree/releases

spring-boot-student-concurrent 工程

layering-cache

为监控而生的多级缓存框架 layering-cache这是我开源的一个多级缓存框架的实现,如果有兴趣可以看一下

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266