首页 > 编程语言 >FutureTask 源码解析

FutureTask 源码解析

时间:2022-11-04 14:08:30浏览次数:70  
标签:int state private 任务 源码 线程 FutureTask 解析


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

类图

FutureTask 源码解析_FutureTask 源码解析

核心属性

/**
*
* 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方法的执行示意图

FutureTask 源码解析_FutureTask_02

总结

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

参考

《java并发编程的艺术》

源码

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

spring-boot-student-concurrent 工程

layering-cache

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


标签:int,state,private,任务,源码,线程,FutureTask,解析
From: https://blog.51cto.com/u_15861563/5823701

相关文章

  • CompletionService 源码解析
    ​​CompletionService​​​的主要作用是:按照异步任务的完成顺序,逐个获取到已经完成的异步任务。主要实现是在​​ExecutorCompletionService​​中。类图核心内部类privat......
  • JAVA并发容器-ConcurrentHashMap 1.7和1.8 源码解析
    HashMap是一个线程不安全的类,在并发情况下会产生很多问题,详情可以参考​​HashMap源码解析​​;HashTable是线程安全的类,但是它使用的是synchronized来保证线程安全,线程竞争......
  • HashMap 源码解析
    源码学习,边看源码边加注释,边debug,边理解。基本属性常量DEFAULT_INITIAL_CAPACITY:默认数组的初始容量-必须是2的幂。MAXIMUM_CAPACITY:数组的最大容量DEFAULT_LOAD_FACTOR:哈......
  • s-sgdisk源码分析 “--set-alignment=value分区对齐参数”
    文章目录​​边界对齐子命令使用​​​​源码分析​​​​sgdisk.ccmain函数入口​​​​gptcl.ccDoOptions解析并执行具体命令函数​​​​gpt.ccCreatePartition创建分......
  • centos下将vim配置为强大的源码阅读器
    每日杂事缠身,让自己在不断得烦扰之后终于有了自己的清静时光来熟悉一下我的工具,每次熟悉源码都需要先在windows端改好,拖到linux端,再编译。出现问题,还得重新回到windows端,这......
  • WCNSS_qcom_cfg.ini WIFI配置文件解析
    STA相关的一般配置gChannelBondingMode5GHz=1gChannelBondingMode24GHz=0//通道绑定gStaKeepAlivePeriod=30//使用非零周期值启用保持活动状态gVhtMpduLen=2......
  • 如何正确学习vue3.0源码
    为什么要学源码技术是第一生产力学习API的设计目的、思路、取舍学习优秀的代码风格学习组织代码的方式学习实现方法的技巧学习ES67新API、TS高级用法不给自......
  • 上帝视角看Vue源码整体架构+相关源码问答
    前言这段时间利用课余时间夹杂了很多很多事把Vue2源码学习了一遍,但很多都是跟着视频大概过了一遍,也都画了自己的思维导图。但还是对详情的感念模糊不清,故这段时间对源码......
  • qt输出自定义的pdf文件源码详解
    qt中有两种方式可以输出pdf:方式1:使用QPrinter即打印机的方式打印pdf这种方式,在qt4成为唯一的方式。QPrinterprinter(QPrinter::HighResolution);//高清晰度printer.set......
  • 设计模式:责任链模式的应用场景及源码应用
    一、概述责任链模式(ChainofResponsibilityPattern)是将链中每一个节点看作是一个对象,每个节点处理的请求均不同,且内部自动维护一个下一节点对象。当一个请求从链式的首......