之前关于Java线程的时候,都是通过实现Runnable接口或者是实现Callable接口,前者交给Thread去run,后者submit到一个ExecutorService去执行。
然后知道了还有个FutrureTask接口,而且好像很有用,在刚看完线程池的相关源码还有点记忆的情况下,就再顺便研究下这个FutureTask吧。
这篇文章会从源码角度探索下FutureTask,然后再研究下ExecutorService的submit方法。
一、FutureTask的类声明
可见它实现了RunnableFuture接口,而这个RunnableFuture接口呢,
这个RuunableFuture接口呢,继承了Runnable接口还有Future。
这意味着FutureTask类可以当作一个Runnable的线程任务类来用,我们可以把它作为参数交给Thread然后搞一条线程来run;
也可以通过这个FutureTask来追踪和控制这个线程的运行,比如可以cancel线程的任务;可以查看完成了没;当然还有最有用堵塞式获得运行的结果get()方法,而且不用像以前一样先把Callable submit到线程池获得一个Future,再通过Future来get。
但问题也来了,Callable的任务可以用Future来get可以理解,因为有个带有返回值的call方法嘛,但这个FutureTask并没有实现Callable接口啊,让我们在下面的分析中看看。
二、FutureTask的应用
两种方式吧:
2.1 FutureTask + Thread
把FutureTask当作Runnable来用
//step1:封装一个计算任务,实现Callable接口 class Task implements Callable<Boolean> { @Override public Boolean call() throws Exception { try { for (int i = 0; i < 10; i++) { Log.d(TAG, "task......." + Thread.currentThread().getName() + "...i = " + i); //模拟耗时操作 Thread.sleep(100); } } catch (InterruptedException e) { Log.e(TAG, " is interrupted when calculating, will stop..."); return false; // 注意这里如果不return的话,线程还会继续执行,所以任务超时后在这里处理结果然后返回 } return true; } } //step2:创建计算任务,作为参数,传入FutureTask Task task = new Task(); FutureTask futureTask = new FutureTask(task); //step3:将FutureTask提交给Thread执行 Thread thread1 = new Thread(futureTask); thread1.setName("task thread 1"); thread1.start(); //step4:获取执行结果,由于get()方法可能会阻塞当前调用线程,如果子任务执行时间不确定,最好在子线程中获取执行结果 try { // boolean result = (boolean) futureTask.get(); boolean result = (boolean) futureTask.get(5, TimeUnit.SECONDS); Log.d(TAG, "result:" + result); } catch (InterruptedException e) { Log.e(TAG, "守护线程阻塞被打断..."); e.printStackTrace(); } catch (ExecutionException e) { Log.e(TAG, "执行任务时出错..."); e.printStackTrace(); } catch (TimeoutException e) { Log.e(TAG, "执行超时..."); futureTask.cancel(true); e.printStackTrace(); } catch (CancellationException e) { //如果线程已经cancel了,再执行get操作会抛出这个异常 Log.e(TAG, "future已经cancel了..."); e.printStackTrace(); }
2.2 FutureTask + ExecutorService
把FutureTask当作Runnable丢给ExecutorService的execute()方法。
//step1 ...... //step2 ...... //step3:将FutureTask提交给线程池执行 ExecutorService executorService = Executors.newCachedThreadPool(); executorService.execute(futureTask); //step4 ......
三、成员变量
3.1 private volatile int state
状态变量,这这代表着FutureTask是有状态的。
FutureTask的所有方法都是围绕这个状态进行的,需要注意,这个值用volatile(易变的)来标记,如果有多个子线程在执行FutureTask,那么它们看到的都会是同一个state,有如下几个值:
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;
NEW
:表示这是一个新的任务,或者还没有执行完的任务,是初始状态。
COMPLETING
:表示任务执行结束(正常执行结束,或者发生异常结束),但是还没有将结果保存到outcome
中。是一个中间状态。
NORMAL
:表示任务正常执行结束,并且已经把执行结果保存到outcome
字段中。是一个最终状态。
EXCEPTIONAL
:表示任务发生异常结束,异常信息已经保存到outcome
中,这是一个最终状态。
CANCELLED
:任务在新建之后,执行结束之前被取消了,但是不要求中断正在执行的线程,也就是调用了cancel(false)
,任务就是CANCELLED
状态,这时任务状态变化是NEW -> CANCELLED。
INTERRUPTING
:任务在新建之后,执行结束之前被取消了,并要求中断线程的执行,也就是调用了cancel(true)
,这时任务状态就是INTERRUPTING
。这是一个中间状态。
INTERRUPTED
:调用cancel(true)
取消异步任务,会调用interrupt()
中断线程的执行,然后状态会从INTERRUPTING
变到INTERRUPTED
。
状态变化有如下4种情况:
NEW -> COMPLETING -> NORMAL --------------------------------------- 正常执行结束的流程
NEW -> COMPLETING -> EXCEPTIONAL ---------------------执行过程中出现异常的流程
NEW -> CANCELLED -------------------------------------------被取消,即调用了cancel(false)
NEW -> INTERRUPTING -> INTERRUPTED -------------被中断,即调用了cancel(true)
3.2 private Callable<V> callable
Callable
类型的变量,封装了计算任务,可获取计算结果。从上面的用法中可以看到,FutureTask
的构造函数中,我们传入的就是实现了Callable的接口的计算任务。Object
类型的变量outcome
,用来保存计算任务的返回结果,或者执行过程中抛出的异常。Callable
任务的线程,runner
在FutureTask中的赋值变化很值得关注,后面源码会详细介绍这个。WaitNode
是FutureTask的内部类,表示一个阻塞队列,如果任务还没有执行结束,那么调用get()获取结果的线程会阻塞,在这个阻塞队列中排队等待。四、构造方法
有两个:
/** * Creates a {@code FutureTask} that will, upon running, execute the * given {@code Callable}. * * @param callable the callable task * @throws NullPointerException if the callable is null */ public FutureTask(Callable<V> callable) { if (callable == null) throw new NullPointerException(); this.callable = callable; this.state = NEW; // ensure visibility of callable } /** * Creates a {@code FutureTask} that will, upon running, execute the * given {@code Runnable}, and arrange that {@code get} will return the * given result on successful completion. * * @param runnable the runnable task * @param result the result to return on successful completion. If * you don't need a particular result, consider using * constructions of the form: * {@code Future<?> f = new FutureTask<Void>(runnable, null)} * @throws NullPointerException if the runnable is null */ public FutureTask(Runnable runnable, V result) { this.callable = Executors.callable(runnable, result); this.state = NEW; // ensure visibility of callable }
可以看到构造器接受一个Callable或者接受一个Runnable和一个代表计算记过的result。
但不管传的是哪个,做的工作都是一样的——1. 将类变量callable,就封装了计算任务的那个类变量,给赋值;2. 将FutureTask的状态设置为NEW。
直接传进来一个Callable的话就直接赋值类变量就可以了,那如果是传进来一个Runnable和Result呢?
源码中可以看到,是用了工具类Executors中的一个方法——Executors.callable(runnable, result);
这个方法就是把一个Runnable加上一个Result封装成一个Callable对象,具体的做法大概就用一个RunnableAdapter的类去实现了Callable接口,然后这个RunnableAdapter类中封装了一个Runnable的task还有个T(泛型)类型的result,重写call方法的适合,就用这个result的T类型作为返回值,然后在call方法中调用task的run方法,最后返回result,看看源码吧:
public static <T> Callable<T> callable(Runnable task, T result) { if (task == null) throw new NullPointerException(); return new RunnableAdapter<T>(task, result); } /** * A callable that runs given task and returns given result */ 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; } }
五、run方法
作为Runnable的实现类,那么肯定要重写run,也就是这个FutureTask作为一个任务,它要干的事。
无论是扔给一个Thread然后start,还是把这个FutureTask交给线程池,调用的都是这里的run方法。
5.1 看源码吧:
public void run() { //1.判断状态是否是NEW,不是NEW,说明任务已经被其他线程执行,甚至执行结束,或者被取消了,直接返回 //2.调用CAS方法,判断RUNNER为null的话,就将当前线程保存到RUNNER中,设置RUNNER失败,就直接返回 if (state != NEW || !U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread())) return; try { Callable<V> c = callable; if (c != null && state == NEW) { V result; boolean ran; try { //3.执行Callable任务,结果保存到result中 result = c.call(); ran = true; } catch (Throwable ex) { //3.1 如果执行任务过程中发生异常,将调用setException()设置异常 result = null; ran = false; setException(ex); } //3.2 任务正常执行结束调用set(result)保存结果 if (ran) set(result); } } finally { // runner must be non-null until state is settled to // prevent concurrent calls to run() //4. 任务执行结束,runner设置为null,表示当前没有线程在执行这个任务了 runner = null; // state must be re-read after nulling runner to prevent // leaked interrupts //5. 读取状态,判断是否在执行的过程中,被中断了,如果被中断,处理中断 int s = state; if (s >= INTERRUPTING) handlePossibleCancellationInterrupt(s); } }
5.2 流程大概就是:
1. 看状态是不是NEW,不是的话直接返回;
2. 如果状态是NEW,那么用UNSAFE的CAS操作来将这个FutureTask中的类变量runner换成当成线程,意味着要有线程来执行这个FutureTask了。
3. 确定没问题后,就调用类变量callable中的call方法,然后将结果用局部变量result保存,如果没问题就调用set(result),如果有异常就setException(ex)。这两个方法其实就把运行结果赋值给类百年来outCome而已。
那就顺便贴一下setException还有set的相关源码还有注解:
protected void setException(Throwable t) { if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {//CAS换状态成中间状态COMPLETING outcome = t;//将结果类变量赋值成抛出的异常,竟然不用volatile也不用CAS! UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state//CAS换成抛异常情况的最终状态 finishCompletion();//任务完成函数,主要是唤醒为了获取这个FutureTask的运行结果的线程 } } protected void set(V v) { if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {//CAS换状态成中间状态COMPLETING outcome = v;//将结果类变量赋值成运行的结果,竟然不用volatile也不用CAS! UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state CAS换成正常运行情况的最终状态 finishCompletion();//任务完成函数,主要是唤醒为了获取这个FutureTask的运行结果的线程 } } /** * Removes and signals all waiting threads, invokes done(), and * nulls out callable. */ private void finishCompletion() { // assert state > COMPLETING; for (WaitNode q; (q = waiters) != null;) {//看似无限循环其实就循环一次,将这个等待队列的对头赋值给q if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {//用CAS操作把等待队列赋值为null,相当于清空等待队列吧 for (;;) {//这个for是遍历等待队列,唤醒他们,不要等了,要么可能现在已经运行完了,有结果了,要么现在抛异常了,挂了 Thread t = q.thread; if (t != null) { q.thread = null; LockSupport.unpark(t);//UNSafe唤醒操作 } WaitNode next = q.next; if (next == null) break; q.next = null; // unlink to help gc q = next; } break; } } done();//空方法,大概是给用户重写的吧,可以在任务结束的时候做点什么吧 callable = null; // to reduce footprint减少可达性?帮助gc吗 }
大概思路就是set完结果后,就调用finishComplemention,在这个方法中会叫醒那些在等任务完成结果的线程,同时会处理这个WaitNode队列(waiters),就把可能有的引用都置null,帮助gc。
从这里我们也可以看出一个问题:
FutureTask
的get(long timeout, TimeUnit unit)
方法,是等待timeout时间后,获取子线程的执行结果,但是如果子任务执行结束了,但是超时时间还没有到,这个方法也会返回结果。
4. finally中,如果state是interrupting,就要handlePossibleCancellationInterrupt(s);
private void handlePossibleCancellationInterrupt(int s) { // It is possible for our interrupter to stall before getting a // chance to interrupt us. Let's spin-wait patiently. if (s == INTERRUPTING) while (state == INTERRUPTING) Thread.yield(); // wait out pending interrupt }
这个中断处理其实就是,如果被中断了,如果状态是INTERRUPTING
,表示正在被中断,这时就让出线程的执行权(yield),给其他线程来执行。
六、get方法
一般情况下,执行任务的线程和获取结果的线程不会是同一个,当我们在主线程或者其他线程中,获取计算任务的结果时,就会调用get方法,如果这时计算任务还没有执行完成,调用get()的线程就会阻塞等待。get()实现如下:
public V get() throws InterruptedException, ExecutionException { int s = state; if (s <= COMPLETING)//说明还没有完成成功 s = awaitDone(false, 0L);//堵塞,或者如果运行任务的线程interrupt,做出回应 return report(s);//返回执行结果,如果抛了异常(就outcome是exception)就抛异常 }
大概可以分为三个步骤:
1. 读取任务的执行状态 state ,如果 state <= COMPLETING
,说明线程还没有执行完(run()中可以看到,只有任务执行结束,或者发生异常的时候,state才会被设置成COMPLETING)。
2. 调用awaitDone(false, 0L)
,进入阻塞状态。看一下awaitDone(false, 0L)
的实现:
/** * Awaits completion or aborts on interrupt or timeout. * * @param timed true if use timed waits * @param nanos time to wait, if timed * @return state upon completion */ 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()) {//看看执行任务的线程是不是interrupt,如果是的话做出反应——1. 在等待队列中移除这个调用get方法的线程结点;2. 抛出异常 removeWaiter(q); throw new InterruptedException(); } int s = state; if (s > COMPLETING) {//任务完成了,可能正常完成也可能抛异常,总之就结束了,就把这个waitNode的thread置空,但其实不太懂,不用remove吗? //还是说1. 等其他进行removeNode的操作的线程会帮忙清除掉?? //还是说2. 等任务完成后,在set方法或者是setException中的finishComplemention那里要唤醒等待线程,顺便赋值null帮助gc的操作那里一起 //处理。 if (q != null) q.thread = null; return s; } else if (s == COMPLETING) // cannot time out yet 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) {//超时的话,移除这个因为get方法堵塞的线程wait结点,并返回 removeWaiter(q); return state; } LockSupport.parkNanos(this, nanos);//不超时的话就堵塞这个设定的时间咯 } else LockSupport.park(this);//普通get方法但还未运行成功,堵塞 } }
awaitDone主要有几个步骤:
a. 判断Thread.interrupted()
,如果调用get()
的线程被中断了,就从等待的线程栈(其实就是一个WaitNode节点队列或者说是栈)中移除这个等待节点,然后抛出中断异常。
b. 读取state,如果s > COMPLETING,表示任务已经执行结束,或者发生异常结束了,此时,调用get()的线程就不会阻塞;如果s == COMPLETING,表示任务结束(正常/异常),但是结果还没有保存到outcome字段,当前线程让出执行权,给其他线程先执行。
c. 如果state是==COMPLETING,意味着基本完成但还没保存结果,就yield。(emmm应该是先放一下处理机,然后等等获得处理机继续处理??)
d. 判断q == null,如果等待节点q为null,就创建等待节点,这个节点后面会被插入阻塞队列。
e. 判断queued,这里是将c中创建节点q加入队列头。使用Unsafe
的CAS方法,对waiters
进行赋值,waiters
也是一个WaitNode节点,相当于队列头,或者理解为队列的头指针。通过WaitNode可以遍历整个阻塞队列。
f. 之后,判断timed
,设置了超时。超时的时间是从get()
传入的值。设置超时时间之后,调用get()
的线程最多阻塞nanos,就会从阻塞状态醒过来。如果超时的话,就移除这个因为get方法堵塞的线程wait结点,并返回state
g. 剩下的一个else,也就是没有设置超时时间但任务又还没执行出结果,就直接进入阻塞状态,等待被其他线程唤醒。
awaitDone()
方法内部有一个无限循环,看似有很多判断,比较难理解,其实这个循环最多循环3次。
假设Thread A执行了get()获取计算任务执行结果,但是子任务还没有执行完,而且Thread A没有被中断,它会进行以下步骤。
step1:Thread A执行了awaitDone()
,1,2两次判断都不成立,Thread A判断q=null,会创建一个WaitNode节点q,然后进入第二次循环。
step2:第二次循环,判断4不成立,此时将step1创建的节点q加入队列头。
step3:第三次循环,判断是否设置了超时时间,如果设置了超时时间,就阻塞特定时间,否则,一直阻塞,等待被其他线程唤醒。
3. 从awaitDone()
返回,最后调用report(int s),这个方法就是把类变量outcome看情况返回而已。
简单看看report(int state)源码咯
/** * Returns result or throws exception for completed task. * * @param s completed state value */ @SuppressWarnings("unchecked") 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); }
通常调用cancel()的线程和执行子任务的线程不会是同一个。当FutureTask的cancel(boolean mayInterruptIfRunning)方法被调用时,如果子任务还没有执行,那么这个任务就不会执行了,如果子任务已经执行,且mayInterruptIfRunning=true,那么执行子任务的线程会被中断(注意:这里说的是线程被中断,不是任务被取消),下面看一下这个方法的实现:
7.1 cancel源码:
public boolean cancel(boolean mayInterruptIfRunning) { //1.判断state是否为NEW,如果不是NEW,说明任务已经结束或者被取消了,该方法会执行返回false //state=NEW时,判断mayInterruptIfRunning,如果mayInterruptIfRunning=true,说明要中断任务的执行,NEW->INTERRUPTING //如果mayInterruptIfRunning=false,不需要中断,状态改为CANCELLED if (!(state == NEW && U.compareAndSwapInt(this, STATE, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED))) return false; try { // in case call to interrupt throws exception if (mayInterruptIfRunning) {//只有是mayInterruptIfRunning才进来,所以如果这个值为false的话,就不会进来,也就是状态就:NEW——》CANCELLED try { //2.读取当前正在执行子任务的线程runner,调用t.interrupt(),中断线程执行 Thread t = runner; if (t != null) t.interrupt(); } finally { // final state //3.修改状态为INTERRUPTED U.putOrderedInt(this, STATE, INTERRUPTED); } } } finally { finishCompletion(); } return true; }
7.2 流程分析:
cancel()
分析:
1. 判断state
,保证state = NEW
才能继续cancel()
的后续操作。如果不是NEW,直接return false; state=NEW
且mayInterruptIfRunning=true
,说明要中断任务的执行,此时,NEW->INTERRUPTING。然后读取当前执行任务的线程runner
,调用t.interrupt()
,中断线程执行,NEW->INTERRUPTING->INTERRUPTED,最后调用finishCompletion()
。
2. 如果mayInterruptIfRunning为false,那么cancel()
方法,只是修改了状态,NEW->CANCELLED,然后直接调用finishCompletion()
。
所以cancel(true)
方法,只是调用t.interrupt()
,此时,如果t
因为sleep(),wait()
等方法进入阻塞状态,那么阻塞的地方会抛出InterruptedException
;如果线程正常运行,需要结合Thread
的interrupted()
方法进行判断,才能结束,否则,cancel(true)
不能结束正在执行的任务。
这也就可以解释前面我遇到的问题,有的情况下,使用 futuretask.cancel(true)方法并不能真正的结束子任务执行。
然后最后也是调用这个finishComplish方法,这个方法其实在上面介绍set和setException,竟然用了那么多次,下面再重点提一下吧(虽然不难hh)
八、子线程返回的组后一步——finishCompletion()的介绍
8.1 这个方法用到了很多次,就我们上面的分析,就在好几个地方用到了:
1. set方法中;
2. setException方法中;
3. cancel方法中。
他们都有一个特点,就是FutureTask准备进入最终状态final state的时候调用这个方法。
8.2 看源码吧:
/** * Removes and signals all waiting threads, invokes done(), and * nulls out callable. */ private void finishCompletion() { // assert state > COMPLETING; for (WaitNode q; (q = waiters) != null;) {//看似无限循环其实就循环一次,将这个等待队列的对头赋值给q if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {//用CAS操作把等待队列赋值为null,相当于清空等待队列吧 for (;;) {//这个for是遍历等待队列,唤醒他们,不要等了,要么可能现在已经运行完了,有结果了,要么现在抛异常了,挂了 Thread t = q.thread; if (t != null) { q.thread = null; LockSupport.unpark(t);//UNSafe唤醒操作 } WaitNode next = q.next; if (next == null) break; q.next = null; // unlink to help gc q = next; } break; } } done();//空方法,大概是给用户重写的吧,可以在任务结束的时候做点什么吧 callable = null; // to reduce footprint减少可达性?帮助gc吗 }
8.3 流程分析
这个代码其实就做了三件事:
1. 用UNSAFE的解除阻塞的方法,将还在等这个FutureTask的get结果的线程都唤醒;
2. 将waiters指向null,将链表中的next都指向null,同时也将每个结点的thread类变量指向null,这一步是为了帮助GC这个waiters等待队列。
3. 设置类变量callable
为null,callable
是FutureTask
封装的任务,任务执行完,将callable置为null帮助gc吧。
哦还有个done方法,这个方法什么都没有做,不过子类可以实现这个方法,做一些额外的操作。
8.4 tips:
所以,
FutureTask的get(long timeout, TimeUnit unit)方法,表示阻塞timeout时间后,获取子线程的执行结果,但是如果子任务执行结束了,但是超时时间还没有到,这个方法也会返回结果。
因为任务执行完之后,会在set或者setException方法中调用这个finishCompletion方法,这个方法会遍历阻塞队列,唤醒阻塞的线程。
LockSupport.unpark(t)执行之后,阻塞的线程会从LockSupport.park(this)/LockSupport.parkNanos(this, parkNanos)醒来,然后会继续进入awaitDone(boolean timed, long nanos)的for无限循环,此时,state >= COMPLETING,然后从awaitDone()返回。此时,get()/get(long timeout, TimeUnit unit)会继续执行,return report(s)。
九、其他方法
FutureTask
的还有两个方法isCancelled()
和isDone()
,其实就是判断state,没有过多的步骤。
public boolean isCancelled() { return state >= CANCELLED; } public boolean isDone() { return state != NEW; }
十、ExecutorService的submit方法
我一开始接触线程池,用的就是这个submit的方法。创建一个类实现Callable接口,然后把它submit给线程池,获得一个Future实例。然后通过这个Future实例,我们可以用get()来获取你的callable任务运行的情况。那么它是怎么实现的呢?
直接看源码吧,我们发现在ThreadPoolExecutor中并没有找到submit方法的实现,于是找AbstractExecutorService类——它是ExecutorService接口的抽象实现类。
果然在这里实现了submit方法。
10.1 AbstractExecutorService中的submit方法源码:
/** * @throws RejectedExecutionException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ public Future<?> submit(Runnable task) { if (task == null) throw new NullPointerException(); RunnableFuture<Void> ftask = newTaskFor(task, null); execute(ftask); return ftask; } /** * @throws RejectedExecutionException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ public <T> Future<T> submit(Runnable task, T result) { if (task == null) throw new NullPointerException(); RunnableFuture<T> ftask = newTaskFor(task, result); execute(ftask); return ftask; } /** * @throws RejectedExecutionException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ public <T> Future<T> submit(Callable<T> task) { if (task == null) throw new NullPointerException(); RunnableFuture<T> ftask = newTaskFor(task); execute(ftask); return ftask; }
可见在这个类中一共重载了三个submit方法,分别以Runnable为参数;以一个Runnable和一个任务执行结果为参数;一个Callable为参数。
我们发现,不管是哪个参数,做的都是把传进来的参数转换成一个RunnableFuture类型的实例。
RunnableFuture??这个接口怎么有点熟悉,没错,就是我们FutureTask实现的接口。
我们看到,这三个submit中都是通过newTaskFor方法来生成RunnableFuture实例的,那就看看这个newTaskFor方法吧:
10.2 newTaskFor方法:
/** * Returns a {@code RunnableFuture} for the given runnable and default * value. * * @param runnable the runnable task being wrapped * @param value the default value for the returned future * @param <T> the type of the given value * @return a {@code RunnableFuture} which, when run, will run the * underlying runnable and which, as a {@code Future}, will yield * the given value as its result and provide for cancellation of * the underlying task * @since 1.6 */ protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) { return new FutureTask<T>(runnable, value); } /** * Returns a {@code RunnableFuture} for the given callable task. * * @param callable the callable task being wrapped * @param <T> the type of the callable's result * @return a {@code RunnableFuture} which, when run, will call the * underlying callable and which, as a {@code Future}, will yield * the callable's result as its result and provide for * cancellation of the underlying task * @since 1.6 */ protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) { return new FutureTask<T>(callable); }
好的现在看到了,原来最后是转换成FutureTask的一个实例!
10.3 好的总结一下线程池的submit流程吧:
1. 传参数给submit,Runnable或者Runnable + result或Callable
2. 如果你传的只有Runnable,那么submit会调用 RunnableFuture<Void> ftask = newTaskFor(task, null),因为你没有定义返回的结果类型,所以直接泛型是void;如果你传的是Runnable + result,会调用newTaskFor(Runnable,result);如果你传的是Callable会调用newTaskFor(Callable)。
3. newTaskFor(Runnable, T result)是通过调用FutureTask<T>(runnable, value);这个构造器做的就是把这个Runnable和result(可能为空)转换成Callable,作为这个FutureTask要执行的任务,用的是工具类Executors.callable(Runnable, result)方法;然后newTaskFor(Callable)就很显然了,就直接把这个Callable作为要执行的任务赋值给这个FutureTask实例。
用一个图来理解过程:
补充下……第一行第三列的那个框搞错了,应该是Callable,懒得改了哈哈哈
4. 调用execute(ftask)来执行刚刚生成的FutureTask封装的任务。之前我们对这个线程池的execute源码的分析我们知道,这个方法接受的是Runnable类型,进来的相当于是Runnable的任务,要么直接新建一个工作线程Worker执行这个Runnable任务;要么在WorkQueue中排队等。而execute线程池在执行execute,主要是通过addWorker来进行相关工作线程的添加还有任务的执行,Worker是一个Runnable和AQS的实现,它是工作线程的抽象代表,它的run方法是不断地去Workqueue里面拿task(其实是我们的FutureTask)来run,然后FutureTask跑起来之后,就进入我们上面讲的FutureTask的逻辑流程了,也可以通过cancel啊get啊等方法来控制这个FutureTask的run过程了。
呼终于有点连起来了………………
参考文章:
https://www.jianshu.com/p/55221d045f39——《可取消的异步任务——FutureTask用法及解析》