在使用線程池時,我們都知道線程池有兩種提交任務的方式,那么他們有什么區別呢?
1.execute提交的是Runnable類型的任務,而submit提交的是Callable或者Runnable類型的任務
2.execute的提交沒有返回值,而submit的提交會返回一個Future類型的對象
3.execute提交的時候,如果有異常,就會直接拋出異常,而submit在遇到異常的時候,通常不會立馬拋出異常,而是會將異常暫時存儲起來,等待你調用Future.get()方法的時候,才會拋出異常
了解了以上區別以后,我們再想想,他們之間會不會有聯系呢?答案是肯定的,其實在submit里面的,任務提交的時候,底層都是使用execute來提交的,我們先來看看源碼
1 public <T> Future<T> submit(Callable<T> task) { 2 if (task == null) throw new NullPointerException(); 3 RunnableFuture<T> ftask = newTaskFor(task); 4 execute(ftask); 5 return ftask; 6 }
上面的代碼中,將 task 傳進一個newTaskFor方法以后,返回一個RunnableFuture對象,然后就直接將這個對象傳入execute方法里面了,所以跟據上面的代碼,可以總結出三點:
1.RunnalbeFuture 是一個實現了Runnable接口的類
2.newTaskFor方法能將Callable任務轉換成Runnable任務
3.通過返回類型可以判斷,RunnableFuture也實現了Future接口,並且由於在方法外,可以通過Future獲得值這一點看,RunnableFuture可以保存值
現在我們來看看具體是怎么實現的
首先看看FutureRunnable的run方法,因為他是runnable任務,被execute提交后肯定會運行這個任務的run方法
1 public void run() { 2 if (state != NEW || 3 !UNSAFE.compareAndSwapObject(this, runnerOffset, 4 null, Thread.currentThread())) 5 return; 6 try { 7 Callable<V> c = callable; 8 if (c != null && state == NEW) { 9 V result; 10 boolean ran; 11 try { 12 result = c.call(); 13 ran = true; 14 } catch (Throwable ex) { //捕獲所有異常 15 result = null; 16 ran = false; 17 setException(ex); //有異常就保存異常 18 } 19 if (ran) 20 set(result); //沒有異常就設置返回值 21 } 22 } finally { 23 // runner must be non-null until state is settled to 24 // prevent concurrent calls to run() 25 runner = null; 26 // state must be re-read after nulling runner to prevent 27 // leaked interrupts 28 int s = state; 29 if (s >= INTERRUPTING) 30 handlePossibleCancellationInterrupt(s); 31 } 32 }
可以看到,runnable 的 run 方法里,直接調用了他封裝的 callable 任務的 call()方法 ,如果有異常,就直接將異常放入 這個類的靜態變量里 ,如果沒有異常,就將返回值放入自己的局部變量里,我們來看看上面代碼中的Set(result)方法吧
1 protected void set(V v) { 2 if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) { 3 outcome = v; 4 UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state 5 finishCompletion(); 6 } 7 }
可以看到標紅的部分,其實就是將傳進來的值,保存到一個叫做 outcome 的靜態變量里面了,而相對應的,由於一開始提交任務時返回了本類的引用(Future對象),所以可以通過引用訪問靜態變量的方式,訪問到返回值了,Future.get() 在RunnableFuture中的實現如下
1 public V get() throws InterruptedException, ExecutionException { 2 int s = state; 3 if (s <= COMPLETING) //如果線程還沒運行完成 4 s = awaitDone(false, 0L); //阻塞等待 5 return report(s); //返回值或者異常 6 }
上面代碼中的report(s)實現如下:
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); }
現在我們再總結一下吧:首先callable沒有什么神奇之處,通過submit提交之后,會被包裝成RunnableFuture,同時被當作返回值傳回,在RunnableFuture的run方法中,會調用它保存的callable任務的call方法,同時跟據是否有異常,來決定保存返回值或者異常到其靜態變量中,最后外部通過get方法就可以訪問到返回值啦!
小技巧:在使用future.get() 獲取返回值的時候,如果當前這個值還沒有計算出來,那么就會產生阻塞,直到獲取到值,這個時候我們可以用 future.isDone 檢查任務的狀態,再根據狀態去get這個值,這樣就不會阻塞了
請尊重作者的辛苦付出,如果轉載請注明出處,謝謝