有时候我们可能会遇到这样的需要,线程执行完后需要返回运行结果,这个时候该怎么做呢?其实这时候就需要用到FutureTask了,先来看个demo:
private void testFutureTask() throws Exception { FutureTask<String> task = new FutureTask<>(new Runnable() { @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { (); } } },"gogo"); new Thread(task).start(); boolean done = (); long l1 = System.currentTimeMillis(); String s = ();//这个方法会阻塞,直到任务完成时会返回 boolean done1 = (); 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<V> { //取消任务,如果任务正在运行的,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<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 = (runnable, result); this.state = NEW; // ensure visibility of callable } static final class RunnableAdapter<T> implements Callable<T> { 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。这是一个中间状态。最终状态是INTERRUPTED private static final int INTERRUPTING = 5; //调用interrupt()中断任务,中断任务的最终状态 private static final int INTERRUPTED = 6;
现在就来看看线程开始执行的run()方法,FutureTask--->run():
public void run() { //如果state不是NEW状态,说明任务已经执行过或是被取消了,就直接返回 if (state != NEW || !(this, RUNNER, null, Thread.currentThread())) return; try { Callable<V> c = callable; if (c != null && state == NEW) { V result; boolean ran; try { result = ();//执行任务,这里就说明真正的任务就是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 ((this, STATE, NEW, COMPLETING)) { outcome = v; (this, STATE, NORMAL); // final state finishCompletion(); } }
这里就是对任务状态的一个转换,从COMPLETING转到NORMAL,并将运行结果保存到outcome,setException()的执行逻辑和这一样,最后就是执行finishCompletion():
private void finishCompletion() { // assert state > COMPLETING; //遍历等待的线程节点,被唤醒的线程会各自从awaitDone()方法中的*()阻塞中返回 for (WaitNode q; (q = waiters) != null;) { if ((this, WAITERS, q, null)) { for (;;) { Thread t = ; if (t != null) { = null; //唤醒等待的线程 (t); } WaitNode next = ; if (next == null) break; = 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) = 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 = (this, WAITERS, = 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) //将当前线程挂起指定时间,时间到了没有被唤醒则会抛异常 (this, parkNanos); } else //挂起当前线程 (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()方法时,如果任务没有执行完成,会将当前线程放入阻塞队列等待,当任务执行完后,会唤醒阻塞队列中的线程。