博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
FutureTask使用及解析
阅读量:4180 次
发布时间:2019-05-26

本文共 8491 字,大约阅读时间需要 28 分钟。

        有时候我们可能会遇到这样的需要,线程执行完后需要返回运行结果,这个时候该怎么做呢?其实这时候就需要用到FutureTask了,先来看个demo:

private void testFutureTask() throws Exception {    FutureTask
task = new FutureTask<>(new Runnable() { @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } },"gogo"); new Thread(task).start(); boolean done = task.isDone(); long l1 = System.currentTimeMillis(); String s = task.get();//这个方法会阻塞,直到任务完成时会返回 boolean done1 = task.isDone(); long l2 = System.currentTimeMillis(); System.out.println("任务返回结果 = "+done+" "+done1+" time = "+(l2-l1)+" "+s);}

运行结果:

任务返回结果 = false   true    time = 5001   gogo

可以看到对这个Runnable封装后就可以在任务结束后获取到执行的结果。这个结果就是我们传进去的,如果你想获取里面运行的结果,那就改用构造方法中是Callable接口就可以了。

        再看构造方法之前,先来看看FutureTask的继承关系,这里是网上的一张图:

Runnable这个接口我们都知道,那剩下的就是来看看Future这个接口了:

public interface Future
{ //取消任务,如果任务正在运行的,mayInterruptIfRunning为true时,表明这个任务会被打断的,并返回true; // 为false时,会等待这个任务执行完,返回true;若任务还没执行,取消任务后返回true,如任务执行完,返回false boolean cancel(boolean mayInterruptIfRunning); //判断任务是否被取消了,正常执行完不算被取消 boolean isCancelled(); //判断任务是否已经执行完成,任务取消或发生异常也算是完成,返回true boolean isDone(); //获取任务返回结果,如果任务没有执行完成则等待完成将结果返回,如果获取的过程中发生异常就抛出异常, //比如中断就会抛出InterruptedException异常等异常 V get() throws InterruptedException, ExecutionException; //在规定的时间如果没有返回结果就会抛出TimeoutException异常 V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;}
这个接口可以看成对任务多加一些功能,这些功能包括任务取消、任务是否取消了,任务是否执行完成了,任务执行完后返回的结果是什么。

        现在就来看看FutureTask的构造方法:

public FutureTask(Callable
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}static final class RunnableAdapter
implements Callable
{ final Runnable task; final T result; RunnableAdapter(Runnable task, T result) { this.task = task; this.result = result; } public T call() { task.run(); return result; }}

有两个构造方法,其中传Runnable的最后通过适配器的方式装换成了Callable,这样的话,FutureTask一定就会持有Callable对象的应用,当线程调用到run()方法时,里面调用到Callable的call() 方法就可以了。执行流程可以看成是这样的Runnable(run())--->Callable(call())。由于FutureTask实现了Future接口,这样就可以达到对run()方法实现一个监视的功能,其实就是对线程的一个监视了。线程的执行状态是通过state来保存的,那就来看看state有哪些状态:

/** Possible state transitions: * NEW -> COMPLETING -> NORMAL * NEW -> COMPLETING -> EXCEPTIONAL * NEW -> CANCELLED * NEW -> INTERRUPTING -> INTERRUPTED */private volatile int state;//初始状态,还没有执行的,这个是在构造方法中进行赋值的private static final int NEW          = 0;//任务已经执行完成或者执行任务的时候发生异常,但是任务执行结果或者异常原因还没有//保存到outcome字段(outcome字段用来保存任务执行结果,如果发生异常,则用来保存异常原因)的时候,// 状态会从NEW变更到COMPLETING。但是这个状态会时间会比较短,属于中间状态private static final int COMPLETING   = 1;//任务正常执行完的最终状态private static final int NORMAL       = 2;//任务发生异常时的最终状态private static final int EXCEPTIONAL  = 3;//任务取消时的状态,任务取消当并不中断,即只调用了cancel(false)方法private static final int CANCELLED    = 4;//用户调用了cancel(true)方法取消任务,状态会从NEW转化为INTERRUPTING。这是一个中间状态。最终状态是INTERRUPTEDprivate static final int INTERRUPTING = 5;//调用interrupt()中断任务,中断任务的最终状态private static final int INTERRUPTED  = 6;

        现在就来看看线程开始执行的run()方法,FutureTask--->run():

public void run() {    //如果state不是NEW状态,说明任务已经执行过或是被取消了,就直接返回    if (state != NEW || !U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))        return;    try {        Callable
c = callable; if (c != null && state == NEW) { V result; boolean ran; try { result = c.call();//执行任务,这里就说明真正的任务就是Callable中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 interrupts int s = state; //任务中断时执行 if (s >= INTERRUPTING) handlePossibleCancellationInterrupt(s); }}
可以看到这里对任务主要有三种处理,正常执行完、发生异常、或是中断,这三种处理正是对应上面state的几种状态,这里我们先来看下正常执行完的情况set():

protected void set(V v) {    if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {        outcome = v;        U.putOrderedInt(this, STATE, NORMAL); // final state        finishCompletion();    }}

这里就是对任务状态的一个转换,从COMPLETING转到NORMAL,并将运行结果保存到outcome,setException()的执行逻辑和这一样,最后就是执行finishCompletion():

private void finishCompletion() {    // assert state > COMPLETING;    //遍历等待的线程节点,被唤醒的线程会各自从awaitDone()方法中的LockSupport.park*()阻塞中返回    for (WaitNode q; (q = waiters) != null;) {        if (U.compareAndSwapObject(this, WAITERS, 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}

你调用get()(后面会分析到)方法获取结果时,如果任务还没有执行完成时,那么就会处于等待状态,那么这个等待的线程就会加入到waiters中,如果你没有调用到get()方法,那么这个for循环就不会执行,说到这,你也许就明白了,就是唤醒在等待这个任务结果的线程并释放资源。

        既然说到获取结果的get()方法,那就来看看这个方法:

public V get() throws InterruptedException, ExecutionException {    int s = state;    if (s <= COMPLETING)        s = awaitDone(false, 0L);    return report(s);}

任务还没执行完就会进入到awaitDone()这个方法中:

private int awaitDone(boolean timed, long nanos) throws InterruptedException {    long startTime = 0L;    // Special value 0L means not yet parked    WaitNode q = null;    boolean queued = false;    for (;;) {        int s = state;        //任务完成、取消或是异常进入,会返回执行完后的状态        if (s > COMPLETING) {            if (q != null)                q.thread = null;            return s;        }        //肯能任务线程被阻塞了,当前线程出让执行权        else if (s == COMPLETING)            // We may have already promised (via isDone) that we are done            // so never return empty-handed or throw InterruptedException            Thread.yield();        //线程中断了,移除等待线程并抛出异常        else if (Thread.interrupted()) {            removeWaiter(q);            throw new InterruptedException();        }        //当前等待节点为null,则初始化新节点并关联到当前线程        else if (q == null) {            if (timed && nanos <= 0L)                return s;            q = new WaitNode();        }        //将当前节点添加到WAITERS,等待任务完成时被唤醒        else if (!queued)            queued = U.compareAndSwapObject(this, WAITERS,                    q.next = waiters, q);        else if (timed) {            final long parkNanos;            if (startTime == 0L) { // first time                startTime = System.nanoTime();                if (startTime == 0L)                    startTime = 1L;                parkNanos = nanos;            } else {                long elapsed = System.nanoTime() - startTime;                if (elapsed >= nanos) {                    removeWaiter(q);                    return state;                }                parkNanos = nanos - elapsed;            }            // nanoTime may be slow; recheck before parking            if (state < COMPLETING)                //将当前线程挂起指定时间,时间到了没有被唤醒则会抛异常                LockSupport.parkNanos(this, parkNanos);        }        else            //挂起当前线程            LockSupport.park(this);    }}

这是一个死循环,不过这里面有一个阻塞,会将当前线程挂起,当任务完成时会被唤醒,唤醒在finishCompletion()中完成,前面有分析到,唤醒后会将执行的状态返回state。

        这样就进入到get()的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);}

还记得前面的set()方法么,在那里将运行结果赋值给outcome,如果是正常运行完毕的,那就将结果返回,如果不是,那就将抛出相应的异常。到这里,FutureTak就分析的差不多了,如果有不懂的地方,可自行看看源码,它的其他方法还是比较好看懂的,如果有分析不对的地方还望指正,一起学习。

总结:

        其实FutureTask还是比较简单,Callable就是他的任务,而FutureTask内部维护了一个任务状态,所有的状态都是围绕这个任务来进行的,随着任务的进行,状态也在不断的更新。任务发起者调用get()方法时,如果任务没有执行完成,会将当前线程放入阻塞队列等待,当任务执行完后,会唤醒阻塞队列中的线程。

你可能感兴趣的文章
《C预处理》之#ifndef
查看>>
Android边录边播应用
查看>>
《Linux内核编程》第十三章:Linux对进程内存的二级页式管理
查看>>
ARM协处理器
查看>>
《miniOS分析》前言
查看>>
《Linux内核编程》第十四章:Linux驱动基础
查看>>
Linux平台下ARM-Linux交叉编译工具链
查看>>
Window平台下ADS自带ARMCC编译工具链
查看>>
micro2440/tiny6410使用JLINK直接烧录nand flash
查看>>
C编译器、连接器与可执行机器码文件
查看>>
android linker 浅析
查看>>
802.11 traffic id
查看>>
Android系统wifi分析-手动连接过程
查看>>
设置IP别名Shell脚本
查看>>
Source Insight 宏-单行注释
查看>>
levelDB源码分析-Arena
查看>>
levelDB源码分析-SSTable
查看>>
平滑升级Nginx的Shell脚本
查看>>
SSH远程会话管理工具
查看>>
canvas标签设长宽是在css中还是在标签中
查看>>