线程池续:你必须要知道的线程池submit()实现原理之FutureTask!
前言
上一篇内容写了Java
中线程池的实现原理及源码分析,说好的是实实在在的大满足,想通过一篇文章让大家对线程池有个透彻的了解,但是文章写完总觉得还缺点什么?
上篇文章只提到线程提交的execute()
方法,并没有讲解线程提交的submit()
方法,submit()
有一个返回值,可以获取线程执行的结果Future<T>
,这一讲就那深入学习下submit()
和FutureTask
实现原理。
使用场景&示例
使用场景
我能想到的使用场景就是在并行计算的时候,例如一个方法中调用methodA()、methodB()
,我们可以通过线程池异步去提交方法A、B,然后在主线程中获取组装方法A、B计算后的结果,能够大大提升方法的吞吐量。
使用示例
/**
* @author wangmeng
* @date 2020/5/28 15:30
*/
public class FutureTaskTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService threadPool = Executors.newCachedThreadPool();
System.out.println("====执行FutureTask线程任务====");
Future<String> futureTask = threadPool.submit(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("FutureTask执行业务逻辑");
Thread.sleep(2000);
System.out.println("FutureTask业务逻辑执行完毕!");
return "欢迎关注: 一枝花算不算浪漫!";
}
});
System.out.println("====执行主线程任务====");
Thread.sleep(1000);
boolean flag = true;
while(flag){
if(futureTask.isDone() && !futureTask.isCancelled()){
System.out.println("FutureTask异步任务执行结果:" + futureTask.get());
flag = false;
}
}
threadPool.shutdown();
}
}
上面的使用很简单,submit()
内部传递的实际上是个Callable
接口,我们自己实现其中的call()
方法,我们通过futureTask
既可以获取到具体的返回值。
submit()实现原理
submit()
是也是提交任务到线程池,只是它可以获取任务返回结果,返回结果是通过FutureTask
来实现的,先看下ThreadPoolExecutor
中代码实现:
public class ThreadPoolExecutor extends AbstractExecutorService {
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
}
public abstract class AbstractExecutorService implements ExecutorService {
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
}
提交任务还是执行execute()
方法,只是task
被包装成了FutureTask
,也就是在excute()
中启动线程后会执行FutureTask.run()
方法。
再来具体看下它执行的完整链路图:
上图可以看到,执行任务并返回执行结果的核心逻辑实在FutureTask
中,我们以FutureTask.run/get
两个方法为突破口,一点点剖析FutureTask
的实现原理。
FutureTask源码初探
先看下FutureTask
中部分属性:
public class FutureTask<V> implements RunnableFuture<V> {
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;
private volatile Thread runner;
private volatile WaitNode waiters;
}
- state
当前task状态,共有7中类型。
NEW: 当前任务尚未执行
COMPLETING: 当前任务正在结束,尚未完全结束,一种临界状态
NORMAL:当前任务正常结束
EXCEPTIONAL: 当前任务执行过程中发生了异常。
CANCELLED: 当前任务被取消
INTERRUPTING: 当前任务中断中..
INTERRUPTED: 当前任务已中断
- callble
用户提交任务传递的Callable,自定义call方法,实现业务逻辑
- outcome
任务结束时,outcome保存执行结果或者异常信息。
- runner
当前任务被线程执行期间,保存当前任务的线程对象引用
- waiters
因为会有很多线程去get当前任务的结果,所以这里使用了一种stack数据结构来保存
FutureTask.run()实现原理
我们已经知道在线程池runWorker()
中最终会调用到FutureTask.run()
方法中,我们就来看下它的执行原理吧:
具体代码如下:
public class FutureTask<V> implements RunnableFuture<V> {
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 = null;
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
}
首先是判断FutureTask
中state
状态,必须是NEW
才可以继续执行。
然后通过CAS
修改runner
引用为当前线程。
接着执行用户自定义的call()
方法,将返回结果设置到result中,result可能为正常返回也可能为异常信息。这里主要是调用set()/setException()
FutureTask.set()实现原理
set()
方法的实现很简单,直接看下代码:
public class FutureTask<V> implements RunnableFuture<V> {
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL);
finishCompletion();
}
}
}
将call()
返回的数据赋值给全局变量outcome
上,然后修改state
状态为NORMAL
,最后调用finishCompletion()
来做挂起线程的唤醒操作,这个方法等到get()
后面再来讲解。
FutureTask.get()实现原理
接着看下代码:
public class FutureTask<V> implements RunnableFuture<V> {
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
}
如果FutureTask
中state
为NORMAL
或者COMPLETING
,说明当前任务并没有执行完成,调用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;
}
else if (s == COMPLETING)
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);
}
}
这个方法可以说是FutureTask
中最核心的方法了,一步步来分析:
如果timed
不为空,这说明指定nanos
时间还未返回结果,线程就会退出。
q
是一个WaitNode
对象,是将当前引用线程封装在一个stack
数据结构中,WaitNode
对象属性如下:
static final class WaitNode {
volatile Thread thread;
volatile WaitNode next;
WaitNode() { thread = Thread.currentThread(); }
}
接着判断当前线程是否中断,如果中断则抛出中断异常。
下面就进入一轮轮的if... else if...
判断逻辑,我们还是采用分支的方式去分析。
分支一:if (s > COMPLETING) {
此时get()
方法已经有结果了,无论是正常返回的结果,还是异常、中断、取消等,此时直接返回state
状态,然后执行report()
方法。
分支二:else if (s == COMPLETING)
条件成立,说明当前任务接近完成状态,这里让当前线程再释放cpu
,进行下一轮抢占cpu
。
分支三:else if (q == null)
第一次自旋执行,WaitNode
还没有初始化,初始化q=new WaitNode();
分支四:else if (!queued){
queued
代表当前线程是否入栈,如果没有入栈则进行入栈操作,顺便将全局变量waiters
指向栈顶元素。
分支五/六:LockSupport.park
如果设置了超时时间,则使用parkNanos
来挂起当前线程,否则使用park()
经过这么一轮自旋循环后,如果执行call()
还没有返回结果,那么调用get()
方法的线程都会被挂起。
被挂起的线程会等待run()
返回结果后依次唤醒,具体的执行逻辑在finishCompletion()
中。
最终stack
结构中数据如下:
FutureTask.finishCompletion()实现原理
具体实现代码如下:
private void finishCompletion() {
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;
q = next;
}
break;
}
}
done();
callable = null;
}
代码实现很简单,看过get()
方法后,我们知道所有调用get()
方法的线程,在run()
还没有返回结果前,都会保存到一个有WaitNode
构成的statck
数据结构中,而且每个线程都会被挂起。
这里是遍历waiters
栈顶元素,然后依次查询起next
节点进行唤醒,唤醒后的节点接着会往后调用report()
方法。
FutureTask.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);
}
这个方法很简单,因为执行到了这里,说明当前state
状态肯定大于COMPLETING
,判断如果是正常返回,那么返回outcome
数据。
如果state
是取消状态,抛出CancellationException
异常。
如果状态都不满足,则说明执行中出现了差错,直接抛出ExecutionException
FutureTask.cancel()实现原理
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;
}
cancel()
方法的逻辑很简单,就是修改state
状态为CANCELLED
,然后调用finishCompletion()
来唤醒等待的线程。
这里如果mayInterruptIfRunning
,就会先中断当前线程,然后再去唤醒等待的线程。
总结
FutureTask
的实现原理其实很简单,每个方法基本上都画了一个简单的流程图来方便立即。
后面还打算分享一个BlockingQueue
相关的源码解读,这样线程池也可以算是完结了。
在这之前可能会先分享一个SpringCloud
常见配置代码分析、最佳实践等手册,方便工作中使用,也是对之前看过的源码一种总结。敬请期待!
欢迎关注:
线程池续:你必须要知道的线程池submit()实现原理之FutureTask!的更多相关文章
- Java线程池ThreadPoolExecutor使用和分析(三) - 终止线程池原理
相关文章目录: Java线程池ThreadPoolExecutor使用和分析(一) Java线程池ThreadPoolExecutor使用和分析(二) - execute()原理 Java线程池Thr ...
- spring动态线程池(实质还是用了java的线程池)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.sp ...
- 线程池是什么?Java四种线程池的使用介绍
使用线程池的好处有很多,比如节省系统资源的开销,节省创建和销毁线程的时间等,当我们需要处理的任务较多时,就可以使用线程池,可能还有很多用户不知道Java线程池如何使用?下面小编给大家分享Java四种线 ...
- Python进阶----异步同步,阻塞非阻塞,线程池(进程池)的异步+回调机制实行并发, 线程队列(Queue, LifoQueue,PriorityQueue), 事件Event,线程的三个状态(就绪,挂起,运行) ,***协程概念,yield模拟并发(有缺陷),Greenlet模块(手动切换),Gevent(协程并发)
Python进阶----异步同步,阻塞非阻塞,线程池(进程池)的异步+回调机制实行并发, 线程队列(Queue, LifoQueue,PriorityQueue), 事件Event,线程的三个状态(就 ...
- 用 ThreadPoolExecutor/ThreadPoolTaskExecutor 线程池技术提高系统吞吐量(附带线程池参数详解和使用注意事项)
1.概述 在Java中,我们一般通过集成Thread类和实现Runnnable接口,调用线程的start()方法实现线程的启动.但如果并发的数量很多,而且每个线程都是执行很短的时间便结束了,那样频繁的 ...
- 线程池 | Java多线程,彻底搞懂线程池
熟悉Java多线程编程的同学都知道,当我们线程创建过多时,容易引发内存溢出,因此我们就有必要使用线程池的技术了. 最近看了一些相关文章,并亲自研究了一下源码,发现有些文章还是有些问题的,所以我也总结了 ...
- Flash图解线程池 | 阿里巴巴面试官希望问的线程池到底是什么?
前言 前几天小强去阿里巴巴面试Java岗,止步于二面. 他和我诉苦自己被虐的多惨多惨,特别是深挖线程和线程池的时候,居然被问到不知道如何作答. 对于他的遭遇,结合他过了一面的那个嘚瑟样,我深表同情(加 ...
- 零基础学习java------day18------properties集合,多线程(线程和进程,多线程的实现,线程中的方法,线程的声明周期,线程安全问题,wait/notify.notifyAll,死锁,线程池),
1.Properties集合 1.1 概述: Properties类表示了一个持久的属性集.Properties可保存在流中或从流中加载.属性列表中每个键及其对应值都是一个字符串 一个属性列表可包含另 ...
- 线程池的极简用法——内置线程池multiprocessing
大家好,今天博主来分享一个线程池的小捷径--内置线程池的使用方法 一.背景 说道多线程,对变成层有了解的小伙伴一定不陌生,虽然不知道是什么但是也会从各大网站.面试分享等途径听说过.这里就不做过多的介绍 ...
随机推荐
- IoTClientTool自动升级更新
IoTClientTool是什么 IoTClientTool是什么,IoTClientTool是IoTClient开源组件的可视化操的作实现.方便对plc设备和ModBusRtu.BACnet.串口等 ...
- C. Four Segments 前缀后缀
C. Four Segments 这种分成了三个节点一般都可以处理一下前缀处理一下后缀,或者处理一下前面的这个点,处理一下后面的这个点,然后再枚举中间这个点. 如果和中间这个点有关的,那么就可以换一下 ...
- C. Okabe and Boxes 思维 模拟 or 线段树
C. Okabe and Boxes 这个题目是一个有点思维的模拟,当时没有想到, 思维就是这个栈的排序这里,因为每次直接排序肯定会t的,所以不可以这么写,那怎么表示排序呢? 就是直接把栈清空,如果栈 ...
- Q - Play With Sequence HDU - 3971 线段树 重新排序建树
Q - Play With Sequence HDU - 3971 这个题目是一个线段树,比较特别的线段树,就是c询问一定次数之后重新排序建树来优化减低复杂度. 第一次碰到这种题目有点迷. 这个题目写 ...
- uniapp滚动监听元素
鸽了这么久,一晃2个月过去了.自考+上班没时间记录. 前不久看到移动官网上的时间轴效果,看起来不错,我也来试着做一下. 需要元素滚动到视野内加载动画. 插件地址 https://ext.dcloud. ...
- 某科学的PID算法学习笔记
最近,在某社团的要求下,自学了PID算法.学完后,深切地感受到PID算法之强大.PID算法应用广泛,比如加热器.平衡车.无人机等等,是自动控制理论中比较容易理解但十分重要的算法. 下面是博主学习过程中 ...
- 记一次 spinor flash 读速度优化
背景 某个项目使用的介质是 spinor, 其 bootloader 需要从 flash 中加载 os. 启动速度是一个关键指标,需要深入优化.其他部分的优化暂且略过,此篇主要记录对 nor 读速度的 ...
- 如何快速理解Spring中的DI和AOP
前言 Spring框架通过POJO最小侵入性编程.DI.AOP.模板代码手段来简化了Java 开发,简化了企业应用的开发.POJO和模板代码相对来说好理解,本篇重点解读下DI和AOP. 一 DI DI ...
- GNU ARM 汇编基础
ARM GNU汇编基础 0 前言 全文补充提醒: 笔者在阅读ARM官方文档及查阅实际的u-boot源码中的汇编代码后,发现了一些不同于ARM官方文档中的汇编语法,查阅相关资料后,才发现主要由于汇编器的 ...
- org.springframework.web.bind.annotation不存在 site:blog.csdn.net(IDEA中运行springboot时报错)
原因:MAVEN版本与IDEA版本不兼容问题, maven虽然更新比较慢,但是最新的3.6.6在与IDEA2019版本及以下版本兼容时会出现以上问题 解决办法:重新配置一个3.6低级别版本的maven ...