并发编程(四)TaskFuture

ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<Object> future = executorService.submit(() -> {
TimeUnit.SECONDS.sleep(5);
return 5;
});
Object result = future.get();

ExecutorService 异步执行任务返回一个 Future,本节重点分析 Future 的 get 方法是如何拿到返回结果的呢?

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

下面我们重点分析 FutureTask 类

一、基本变量

(1) 核心成员变量

// 1. 执行的回调方法。如果是 Runnable 就通过 Executors#callable 包装成一个 Callable
private Callable<V> callable; // 2. 保存计算结果或者异常信息。non-volatile, protected by state reads/writes
private Object outcome; // 3. 执行 callable 的线程,run 方法中通过 CAS 保证原子性操作
private volatile Thread runner; // 4. 等待结果的线程队列,eg: 不同的线程同时调用 get()
// 这个队列使用 Treiber stack(可以理解为基于 CAS 的无锁的栈,先进后出)
private volatile WaitNode waiters;

(2) 状态变化

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

任务执行正常结束前,state 会被设置成 COMPLETING,代表任务即将完成,接下来很快就会被设置为 NARMAL 或者 EXCEPTIONAL,这取决于调用 Runnable 中的 call() 方法是否抛出了异常。有异常则后者,反之前者。

任务提交后、任务结束前取消任务,那么有可能变为 CANCELLED 或者 INTERRUPTED。在调用 cancel 方法时,如果传入 false 表示不中断线程,state 会被置为 CANCELLED,反之 state 先被变为 INTERRUPTING,后变为 INTERRUPTED。

总结下,FutureTask 的状态流转过程,可以出现以下四种情况:

  1. 任务正常执行并返回。 NEW -> COMPLETING -> NORMAL
  2. 执行中出现异常。NEW -> COMPLETING -> EXCEPTIONAL
  3. 任务执行过程中被取消,并且不响应中断。NEW -> CANCELLED
  4. 任务执行过程中被取消,并且响应中断。 NEW -> INTERRUPTING -> INTERRUPTED 

补充:Unsafe

Unsafe 是 JDK 底层的类库,位于 sun.misc.Unsafe 中,在 java.util.concurrent 广泛使用。

private static final UNSAFE = sun.misc.Unsafe.getUnsafe();
private static final long stateOffset = UNSAFE.objectFieldOffset (k.getDeclaredField("state")); // 更新 state 状态
UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)

二、run

/**
* run 方法执行有两个条件:1. state=NEW; 2. runner=null
* 1. 执行前 state=NEW & runner=null
* 2. 执行中 state=NEW & runner=Thread.currentThread()
* 3. 执行后 state!=NEW & runner=null,根据是否有异常执行 set(result) 或 setException(ex),无论执行成功与否都会更新 state 状态
* 因此,多个线程同时调用 run 方法的情况 callable 也只会执行一次
*/
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);
}
// set 方法会回调钩子方法 done(),可能抛出异常
if (ran)
set(result);
}
} finally {
runner = null; // 等待调用 cancel(true) 的线程完成中断,防止中断操作逃逸出 run 或者 runAndReset 方法,影响后续操作
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
} protected void set(V v) {
// 通过 CAS 状态来确认计算没有被取消,而且线程只执行了一次
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();
}
} private void finishCompletion() {
for (WaitNode q; (q = waiters) != null;) {
// 必须将栈顶 CAS 为 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;
// 将 next 域置为 null,这样对 GC 友好
q.next = null;
q = next;
}
break;
}
} /*
* done 方法是暴露给子类的一个钩子方法。
* 这个方法在 ExecutorCompletionService.QueueingFuture 中的 override 实现是把结果加到阻塞队列里。
*/
done(); callable = null;
} private void handlePossibleCancellationInterrupt(int s) {
/*
* 等待调用 cancel(true) 的线程完成中断,防止中断操作逃逸出 run 或者 runAndReset 方法,影响后续操作
*
* 实际上,当前调用 cancel 方法的线程不一定能够中断到本线程。
* 有可能 cancel 方法里读到 runner 是 null,甚至有可能是其它并发调用 run/runAndReset 方法的线程。
* 但是也没办法判断另一个线程在 cancel 方法中读到的 runner 到底是什么,所以索性自旋让出 CPU 时间片也没事。
*/
if (s == INTERRUPTING)
while (state == INTERRUPTING)
Thread.yield();
}

三、get

public V get() throws InterruptedException, ExecutionException {
int s = state;
// 如果线程已经执行完成直接返回
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
} /**
* 等待任务执行完毕,如果任务取消或者超时则停止
* @param timed 为 true 表示设置超时时间
* @param nanos 超时时间
* @return 任务完成时的状态
* @throws InterruptedException
*/
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;
// 1. callable 已执行完成,无论成功或失败直接返回执行结果
if (s > COMPLETING) {
// 已执行完,为了 GC 需要清 q.thread
if (q != null)
q.thread = null;
return s;
}
// 2. COMPLETING 是一个很短暂的状态,调用 Thread.yield 期望让出时间片,之后重试循环
else if (s == COMPLETING)
Thread.yield();
// 3. 初始化节点,重试一次循环
else if (q == null)
q = new WaitNode();
// 4. queued 记录是否已经入栈,此处准备将节点压栈
else if (!queued)
/*
* 这是 Treiber Stack 算法入栈的逻辑。
* Treiber Stack 是一个基于 CAS 的无锁并发栈实现
* 更多可以参考https://en.wikipedia.org/wiki/Treiber_Stack
*/
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
// 5. 如果有时限,判断是否超时,未超时则park剩下的时间。
else if (timed) {
nanos = deadline - System.nanoTime();
// 超时,移除栈中节点
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
LockSupport.parkNanos(this, nanos);
}
else
LockSupport.park(this);
}
} /**
* 清理用于保存等待线程栈里的无效节点,所谓节点无效就是内部的 thread 为 null(类比 ThreadLocalMap)
*
* 一般有以下几种情况:
* 1. 节点调用 get 超时。
* 2. 节点调用 get 中断。
* 3. 节点调用 get 拿到 task 的状态值(> COMPLETING)。
*
* 此方法干了两件事情:
* 1. 置标记参数 node 的 thread 为 null
* 2. 清理栈中的无效节点
*
* 如果在遍历过程中发现有竞争则重新遍历栈。
*/
private void removeWaiter(WaitNode node) {
if (node != null) {
node.thread = null;
retry:
for (;;) { // restart on removeWaiter race
// pre -> current -> next,如果 current 无效就把 pre.next=next
for (WaitNode pred = null, q = waiters, s; q != null; q = s) {
s = q.next;
// 1. 如果当前节点仍有效,则置 pred 为当前节点,继续遍历
if (q.thread != null)
pred = q; // 2. 当前节点已无效且有前驱,则将前驱的后继置为当前节点的后继实现删除节点。
// 如果前驱节点已无效,则重新遍历 waiters 栈。
else if (pred != null) {
pred.next = s;
if (pred.thread == null)
continue retry;
}
// 3. 当前节点已无效,且当前节点没有前驱,则将栈顶置为当前节点的后继。
// 失败的话重新遍历 waiters 栈。
else if (!UNSAFE.compareAndSwapObject(this, waitersOffset, q, s))
continue retry;
}
break;
}
}
} /**
* 导出结果。
*/
private V report(int s) throws ExecutionException {
Object x = outcome;
// 1. 正常执行完计算任务
if (s == NORMAL)
return (V)x;
// 2. 取消
if (s >= CANCELLED)
throw new CancellationException();
// 3. 执行计算任务时发生异常
throw new ExecutionException((Throwable)x);
}

四、cancal

/**
* mayInterruptIfRunning=false 时,不允许在线程运行时中断,设成 true 的话就允许但不保证一定会中断线程。
* 1. true 时,将状态修改成 INTERRUPTING,执行 thread.interrupt()
* 2. false 时,将状态修改成 CANCELLED
*/
public boolean cancel(boolean mayInterruptIfRunning) {
if (!(state == NEW && UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try {
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
t.interrupt();
} finally {
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
} finally {
finishCompletion();
}
return true;
}

参考:

  1. 《FutureTask 源码解读》:http://www.cnblogs.com/micrari/p/7374513.html

每天用心记录一点点。内容也许不重要,但习惯很重要!

并发编程(四)TaskFuture的更多相关文章

  1. 【Java并发编程四】关卡

    一.什么是关卡? 关卡类似于闭锁,它们都能阻塞一组线程,直到某些事件发生. 关卡和闭锁关键的不同在于,所有线程必须同时到达关卡点,才能继续处理.闭锁等待的是事件,关卡等待的是其他线程. 二.Cycli ...

  2. Java 并发编程(四):如何保证对象的线程安全性

    01.前言 先让我吐一句肺腑之言吧,不说出来会憋出内伤的.<Java 并发编程实战>这本书太特么枯燥了,尽管它被奉为并发编程当中的经典之作,但我还是忍不住.因为第四章"对象的组合 ...

  3. Go并发编程(四)

        并发基础   多进程  多线程 基于回调的非阻塞/异步IO     协程  协程  与传统的系统级线程和进程相比,协程的最大优势在于其“轻量级”,可以轻松创建上百万个而不会导致系统资源衰竭, ...

  4. 并发编程>>四种实现方式(三)

    概述 1.继承Thread 2.实现Runable接口 3.实现Callable接口通过FutureTask包装器来创建Thread线程 4.通过Executor框架实现多线程的结构化,即线程池实现. ...

  5. Java并发编程 (四) 线程安全性

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 一.线程安全性-原子性-atomic-1 1.线程安全性 定义: 当某个线程访问某个类时,不管运行时环境 ...

  6. Java并发编程(四):并发容器(转)

    解决并发情况下的容器线程安全问题的.给多线程环境准备一个线程安全的容器对象. 线程安全的容器对象: Vector, Hashtable.线程安全容器对象,都是使用 synchronized 方法实现的 ...

  7. 并发编程(四):ThreadLocal从源码分析总结到内存泄漏

    一.目录      1.ThreadLocal是什么?有什么用?      2.ThreadLocal源码简要总结?      3.ThreadLocal为什么会导致内存泄漏? 二.ThreadLoc ...

  8. java并发编程的艺术——第四章总结

    第四章并发编程基础 4.1线程简介 4.2启动与终止线程 4.3线程间通信 4.4线程应用实例 java语言是内置对多线程支持的. 为什么使用多线程: 首先线程是操作系统最小的调度单元,多核心.多个线 ...

  9. 并发编程(四):atomic

    本篇博客我们主要讲述J.U.C包下的atomic包,在上篇博客"并发模拟"的最后,我们模拟高并发的情形时出现了线程安全问题,怎么解决呢?其实解决的办法有很多中,如直接在add()方 ...

随机推荐

  1. QT中使用自己定的类和Vector出现错误

    关于QT自定义类不能调用问题: 在Main()函数里面一定要定义include“XXX.cpp”include“XXX.h”,具体原因我也不知道为什么这样定义,是在一个贴吧看见的,弄了好久才解决. 第 ...

  2. C++官方文档-常量成员函数

    #include <iostream> using namespace std; class MyClass { public: int x; static int n; const in ...

  3. OpenACC Julia 图形

    ▶ 书上的代码,逐步优化绘制 Julia 图形的代码 ● 无并行优化(手动优化了变量等) #include <stdio.h> #include <stdlib.h> #inc ...

  4. 使用再生龙Clonezilla备份还原Linux系统

    一位老哥推荐给我的,产地是祖国宝岛台湾,实测效果非常好,解决了我的一个大问题. 为了减少篇幅,方便阅读,把备份还原的过程单独写一篇随笔. 官网简介:http://clonezilla.nchc.org ...

  5. sqlserver主从复制

    参考网站: http://www.178linux.com/9079 https://www.cnblogs.com/tatsuya/p/5025583.html windows系统环境进行主从复制操 ...

  6. centos的安装和下载

    https://blog.csdn.net/risen16/article/details/50737948

  7. mvc框架路由原理

    到目前为止已经使用过很多php框架,比如:Zendframework,ThinkPHP,YII,Slim.但还未静下心来研究过框架的原理. 今天首先来看一下mvc框架中路由的原理: 所谓路由,就是程序 ...

  8. delphi ios grid BindSourceDB bug

    BindSourceDB4.DataSet :=nil; BindSourceDB4.DataSet :=FDMemTable1; grid绑定后显示数据正常,第二次赋值BindSourceDB4.D ...

  9. WDA-BOPF业务对象处理框架

    SAP中的BOPF(Business Object Processing Framework) 正文 希望简化你的业务应用开发过程?业务对象处理框架(Business Object Processin ...

  10. HIBERNATE知识复习记录2-继承关系

    发现了一篇和我类似的学习尚硅谷视频写的文章,内容如下,比我说的详细全面,可以看一下: [原创]java WEB学习笔记87:Hibernate学习之路-- -映射 继承关系(subclass , jo ...