线程池源码分析-FutureTask
1 系列目录
- 线程池接口分析以及FutureTask设计实现
- 线程池源码分析-ThreadPoolExecutor
该系列打算从一个最简单的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 yetThread.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);}elseLockSupport.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 interruptsint 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 statefinishCompletion();}
}protected void setException(Throwable t) {if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {outcome = t;UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final statefinishCompletion();}
}
都是首先将状态变成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 gcq = 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对该核心方法的实现
线程池源码分析-FutureTask相关推荐
- 吐血整理:Java线程池源码分析(基于JDK1.8建议收藏)
文章目录 一.引言 二.线程池的参数介绍 1.ThreadPoolExecutor的UML图 三.线程池的使用 1.线程池的工作原理 2.线程池类型 2.1.newCachedThreadPool使用 ...
- Java线程池 源码分析
1.个人总结及想法: (1)ThreadPoolExecutor的继承关系? ThreadPoolExecutor继承AbstractExectorService,AbstractExecutorSe ...
- java 线程池 源码_java线程池源码分析
我们在关闭线程池的时候会使用shutdown()和shutdownNow(),那么问题来了: 这两个方法又什么区别呢? 他们背后的原理是什么呢? 线程池中线程超过了coresize后会怎么操作呢? 为 ...
- 线程池源码分析之ThreadPoolExecutor
前言 今天老吕给大家来分享下ThreadPoolExecutor 线程池的实现逻辑,大家伙认真看,一般人我不告诉他的. 线程池相关类图 JDK中线程池相关的类结构关系图 获取不同特性的线程池 在Exe ...
- 线程池之ScheduledThreadPoolExecutor线程池源码分析笔记
1.ScheduledThreadPoolExecutor 整体结构剖析. 1.1类图介绍 根据上面类图图可以看到Executor其实是一个工具类,里面提供了好多静态方法,根据用户选择返回不同的线程池 ...
- Zygote pre-fork线程池源码分析
前言 在Android Q上,google为了加快应用的启动速度.在zygote fork阶段,采用了线程池的方式,来加快fork的过程. 首先,如果让我们自己做,肯定会选择java的线程池模型,先创 ...
- java线程池_Java 并发编程 线程池源码实战
作者 | 马启航 杏仁后端工程师.「我头发还多,你们呢?」 一.概述 笔者在网上看了好多的关于线程池原理.源码分析相关的文章,但是说实话,没有一篇让我觉得读完之后豁然开朗,完完全全的明白线程池,要么写 ...
- Java 并发编程 -- 线程池源码实战
一.概述 小编在网上看了好多的关于线程池原理.源码分析相关的文章,但是说实话,没有一篇让我觉得读完之后豁然开朗,完完全全的明白线程池,要么写的太简单,只写了一点皮毛,要么就是是晦涩难懂,看完之后几乎都 ...
- java futuretask 源码解析_Java异步编程——深入源码分析FutureTask
Java的异步编程是一项非常常用的多线程技术. 之前通过源码详细分析了ThreadPoolExecutor<你真的懂ThreadPoolExecutor线程池技术吗?看了源码你会有全新的认识&g ...
最新文章
- 8月第3周回顾:四巨头发三大新闻 一报告引多家争议
- 习题8-5 使用函数实现字符串部分复制 (20 分)
- .Net QQ互联教程
- Web 2.0技术对SEO的影响
- 【javascript】javascript设计模式mixin模式
- (1110, “Column ‘arriveTime‘ specified twice“)
- APP模拟手势高级操作
- Windows系统下nodejs安装及配置
- java下载图片到手机相册_Unity保存图片到Android手机且更新相册
- 绕过模拟器检测_和平精英:光子重点打击外设与模拟器,违规将封禁365天
- ubuntu 12.04 mysql_Ubuntu 12.04 mysql 源码安装--mysql.5.5.x
- Using LogMiner
- Android 学习笔记【基础扫盲篇】
- JMeter接口测试中,响应数据中文显示乱码的处理方法(转)
- Appium系列教程
- SQL日期时间格式转换大全
- pacman+s+java_ArchLinux pacman安装openjdk
- Android 状态栏常规操作(状态栏显示,状态栏颜色,沉浸式状态栏)
- java如何控制远程桌面_Java实现屏幕抓图,控制远程桌面
- 网络基础之DNS、网关
热门文章
- 三维CNN:收集一些最近的3d卷积网络PointNet++
- ObjecT4:On-line multiple instance learning (MIL)学习
- VTK+MFC 系列教程 非常强大
- MicroPython 1.8.6重新支持512K的模块
- Ember版本小小结
- OneAPM Cloud Test——系统性能监控神器
- Qucs 产生大文件的一个bug
- Scott Mitchell 的ASP.NET 2.0数据教程之三十九:: 在编辑和插入界面里添加验证控件...
- org.apache.hadoop.hive.metastore.api.SerDeInfo; local class incompatible
- flink的DAG可视化使用(visualizer的使用)