FutureTask源码分析
来源:互联网 发布:麦德斯 米科尔森 知乎 编辑:程序博客网 时间:2024/05/29 10:12
FutureTask的类图关系如下
1.Runable与Callable
java.lang.Runnable是一个接口,只有一个run()方法
public interface Runnable { public abstract void run(); }
run()方法的返回值是void,故在执行完任务后无法返回任何结果
Callable是java.util.concurrent包下的,也是一个接口,也只有一个call()方法,类似于java.lang.Runnable的run()方法,实现Callable接口的类和实现Runnable接口的类都是可以被其它线程执行的任务
public interface Callable<V> { V call() throws Exception; }
可以看到call()方法是有返回值的,可以将执行的结果返回
Callable和Runnable的区别:
(1)Callable中定义的是call()方法,Runnable中定义的是run()方法
(2)Callable中的call()方法可以返回执行任务后的结果,Runnable中的run()方法无法获得返回值
(3)Callable中的call()方法定义了throws Exception抛出异常,抛出的异常可以在主线程
(4)Future.get()时被主线程捕获;Runnable中的run()方法没有定义抛出异常,运行任务时发生异常时也会上抛,因为即使不加默认也会上抛RuntimeException,但异常无法被主线程获取
(5)运行Callable任务可以拿到一个Future对象代表异步运算的结果
另外,Executors类中内部实现了RunableAdapter类,通过它可以将一个Runable和一个返回值result封装为一个Callable对象。
2.Future
Future是java.util.concurrent包下的一个接口,代表着一个异步计算的结果,可以通过get()获取线程执行的返回值
Future提供了三种功能:
(1)获取任务执行的结果
(2)取消任务
(3)判断任务是否完成 或 是否取消
因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask
3.FutureTask源码分析
public class FutureTask<V> implements RunnableFuture<V>
FutureTask实现了RunnableFuture接口,那么RunnableFuture又是什么呢?
public interface RunnableFuture<V> extends Runnable, Future<V> { /** * Sets this Future to the result of its computation * unless it has been cancelled. */ void run(); }
RunnableFuture接口继承了Runnable和Future,所以它既是一个可以让线程执行的Runnable任务,又是一个可以获取Callable返回值的Future
3.1FutureTask的属性
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;
- callable:表示要运行的任务对象,由构造FutureTask时生成;
- outcome: 是任务执行后的结果或异常
- runner:执行callable的线程;
- waiters:是一个链表结构的头部指针,每一个节点表示等待获取结果的阻塞线程,后等待线程的会排在链表头部;
- state 是任务的运行状态
(1)初始化时是NEW;
(2)任务终止的状态有NORMAL(正常结束)、EXCEPTIONAL(异常结束)、CANCELLED(被取 消)、INTERRUPTED(执行中被中断),这些状态是通过set()、setException、cancel()方法触发的;
(3)COMPLETING 和 INTERRUPTING是两个中间状态,当正常结束设置outcome属性前是COMP LETING,设置后变成NORMAL;当中断运行中线程前是INTERRUPTING,调用thread.interrupt ()后是INTERRUPTED;
3.2构造方法
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}
(1)构造方法参数是Callable定义的任务,并将state置为NEW,只有当state为NEW时,callable才能被执行;
(2)构造方法参数为Runnable和带泛型的result对象,由于Runnable本身是没有返回值的,故线程的执行结果通过result返回;可以看到通过runnable和result封装了个Callable,实际上是new RunnableAdapter(task, result),这个Adapter适配器将Runnable和result转换成Callable,并返回result;
3.3get实现
get()方法有两个实现,一个是一直等待获取结果,直到任务执行完;一个是等待指定时间,超时后任务还未完成会上抛TimeoutException
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}
内部通过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 (;;) { //1.如果调用线程设置了中断标记,则从等待队列中移除节点并抛出异常 if (Thread.interrupted()) { removeWaiter(q); throw new InterruptedException(); } int s = state; // 2. 获取当前状态,如果状态大于COMPLETING // 说明任务已经结束(要么正常结束,要么异常结束,要么被取消) // 则把thread显示置空,并返回结果 if (s > COMPLETING) { if (q != null) q.thread = null; return s; } // 3. 如果状态处于中间状态COMPLETING // 表示任务已经结束但是任务执行线程还没来得及给outcome赋值 // 这个时候让出执行权让其他线程优先执行,等待状态发生改变后返回 else if (s == COMPLETING) // cannot time out yet Thread.yield(); else if (q == null) // 4. 如果等待节点为空,则构造一个等待节点 q = new WaitNode(); else if (!queued) // 5.如果还没有入队列,则把当前节点加入waiters指向的队列的对头 queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q); else if (timed) { // 如果需要等待特定时间,则先计算要等待的时间 // 如果已经超时,则删除对应节点并返回对应的状态 nanos = deadline - System.nanoTime(); if (nanos <= 0L) { removeWaiter(q); return state; } // 6.阻塞等待特定时间 LockSupport.parkNanos(this, nanos); } else // 7.阻塞等待直到被其他线程唤醒 LockSupport.park(this); }}
上面这段代码有两个生僻函数的使用
(1) compareAndSwapObject
改方法解决的是并发中原子的更新引用对象
//如果offset指向的值等于expected,则原子的将Java变量更新为x
compareAndSwapObject(Object o, long offset, object expected, object x)
(2)LockSupport
LockSupport为Java并发的工具类,park表示表示阻塞当前线程,另外还有unpark表示唤醒阻塞的线程;
awaitDone方法执行的结果就是,在任务未执行结束时,所有希望获取结果的线程都将在waiters指向的队列中进行等待;添加的顺序为,后执行get的线程被添加在队首。在threadA和threadB都阻塞等待之后的waiters结果如图。
report方法根据awaitDown返回的状态进行判断,然后返回任务执行的结果或异常。
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);}
3.4run实现
public void run() {//1 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 { //2 result = c.call(); ran = true; } catch (Throwable ex) { result = null; ran = false; //3 setException(ex); } //4 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 interrupts int s = state; //5 if (s >= INTERRUPTING) handlePossibleCancellationInterrupt(s); }}
(1) 标记1
状态如果不是NEW,说明任务或者已经执行过,或者已经被取消,直接返回;状态如果是NEW,则尝试把当前执行线程保存在runner字段中;
(2) 标记2
执行任务,如果任务执行过程中出现了异常则执行“标记3”,顺利的话就会执行“标记4”;
(3) 标记3
protected void setException(Throwable t) { if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) { outcome = t; UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state finishCompletion(); }}
原子的改变状态为COMPLETING,然后将异常结果复制给outcome,再将状态设置为EXCEPTIONAL;这样就完成了前面说的状态变化过程:NEW->COMPLETING->EXCEPTIONAL,并且将最终结果保存在outcome中;最后调用finishCompletion方法,唤醒所有等待的线程并返回结果outcome。
(4) 标记4
set方法和setException方法调用基本相同,唯一不同的是状态变化过程:NEW->COMPLETING->NORMAl;
(5) 标记5
如果执行任务时,cancel被调用了,那么状态将被修改为INTERRUPTING或CANCELLED,handlePossibleCancellationInterrupt方法将被执行
private void handlePossibleCancellationInterrupt(int s) { if (s == INTERRUPTING) while (state == INTERRUPTING) Thread.yield(); // wait out pending interrupt}
该方法的理解需要结合cancel方法进行应用场景的举例:
当任务正在执行但并未结束,用户调用了cancel方法且参数为true,那么状态被设置INTERRUPTING
,此时任务执行完成开始调用handlePossibleCancellationInterrupt方法,因为cancel方法还未执行如下代码
if (mayInterruptIfRunning) { try { Thread t = runner; if (t != null) t.interrupt(); } finally { // final state UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED); }}
所以会调用Thread.yield去放弃cpu的资源停止运行,直到上面的代码被执行状态变INTERRUPTED,最后任务运行结束。
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; // unlink to help gc q = next; } break; }}done();callable = null; // to reduce footprint
其核心作用就是换新waiters队列中所有等待的线程,唤醒的方法就是使用LockSupport.unpark,在get方法中是使用的park方法让线程休眠的。
3.5cancel实现
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 { finishCompletion(); } return true;}
(1) 修改状态为NEW-> INTERRUPTING-> INTERRUPTED或NEW-> CANCELLED;
(2) 如果是调用参数为true,则会设置认为执行线程的中断标记,这个中断标记如果是单纯的从FutrueTask来看是没有任何作用的,它的作用在于callable所执行的任务是否调用了阻塞方法并且捕获了InterruptedException异常进行处理,否则调用t.interrupt并无作用;
(3) 最后调用finishCompletion线程唤醒等待队列;
4思考
Future.cancel()真的能取消任务的执行吗?
答案是“不一定”,根据JDK中的方法注释“Attempts to cancel execution of this task”,即尝试去取消执行的任务;
如果任务正在执行,且调用cancel()时参数mayInterruptIfRunning传的是true,那么会对执行线程调用interrupt()方法。
那么问题就变成了interrupt()方法能中断线程执行吗?
interrupt()方法不会中断正在运行的线程。这一方法实际上完成的是在线程受到阻塞时抛出一个中断信号,这样线程就得以退出阻塞的状态。更确切的说,如果线程被Object.wait()、Thread.join()、Thread.sleep()等阻塞,那么它将接收到一个中断异常(InterruptedException),从而提早地终结被阻塞状态。
如果线程没有被阻塞,调用interrupt()将不起作用
那么即使线程正在阻塞状态,并抛出了InterruptedException,线程能否真的取消执行还要看代码中是否捕获了InterruptedException和有没有做相应的对中断标示的判断逻辑
- FutureTask 源码分析
- FutureTask源码分析
- JUC - FutureTask 源码分析
- FutureTask 源码分析
- 源码分析-FutureTask
- FutureTask源码分析
- FutureTask源码深入分析
- FutureTask 源码分析
- FutureTask源码分析
- FutureTask源码分析(JDK 1.7)
- JAVA FutureTask之AbstractQueuedSynchronizer 源码分析
- 《Java源码分析》:Future、RunnableFuture、FutureTask
- JUC源码分析27-线程池-FutureTask
- 《Java源码分析》:Future、RunnableFuture、FutureTask
- 干货|Java Concurrent -- FutureTask 源码分析
- 多线程之Callable接口及FutureTask源码分析
- Java多线程 -- JUC包源码分析13 -- Callable/FutureTask源码分析
- java FutureTask 源码解析
- 十一周项目一
- Java RSA 加密
- for与for in的区别 -记录
- OpenMp之sections用法
- 【特色】Hello,未来网络 –SDN实战特训营(第X期)
- FutureTask源码分析
- Flask简介与简单项目操作流程
- Hadoop核心之HDFS 架构设计
- Unity Shader 扰动效果
- Airbnb从微服务演进过程中获得的十条经验
- git在工作中常用的命令
- Proxy--Android的设计模式之代理模式的讲解
- meta 属性 http-equiv的值及其含义(转)
- JAVA泛型常见误区