詳解線程池execute和submit用法


在使用線程池時,我們都知道線程池有兩種提交任務的方式,那么他們有什么區別呢?

 

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這個值,這樣就不會阻塞了

請尊重作者的辛苦付出,如果轉載請注明出處,謝謝

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM