java callable執行過程
前言
我們常用的建立執行緒方式一般有下面 2 種:
繼承Thread,重寫run方法
實現Runnable介面,重新run方法
其實在 Executor 框架中還有一種方法可以實現非同步,那就是實現 Callable 介面並重寫call方法。雖然是實現 Callable ,但是在 Executor 實際執行時,會將 Runnable 的例項或 Callable 的例項轉化為 RunnableFuture 的例項,而 RunnableFuture 繼承了 Runnable 和 Future 介面,這點將在下文詳細解釋。瞭解到這些 ,那麼它和 Runnable 有什麼不同呢? Callable 與 Runnable 相比有以下 2 點不同:
Callable 可以在任務結束的時候提供一個返回值,Runnable 無法提供這個功能
Callable 的 call 方法分可以丟擲異常,而 Runnable 的 run 方法不能丟擲異常。
實現原理
在介紹 Callable 的實現原理前,我們先看看它是怎麼使用的:
public class ThreadTest { public static void main(String[] args) { System.out.println("main start"); ExecutorService threadPool = Executors.newSingleThreadExecutor();// Future<?> future = threadPool.submit(new MyRunnable()) ; Future<String> future = threadPool.submit(new MyCallable()); try { // 這裡會發生阻塞 System.out.println(future.get()); } catch (Exception e) { } finally { threadPool.shutdown(); } System.out.println("main end"); } } public class MyCallable implements Callable<String> { @Override public String call() throws Exception { // 模擬耗時任務 Thread.sleep(3000); System.out.println("MyCallable 執行緒:" + Thread.currentThread().getName()); return "MyCallable" ; } } public class MyRunnable implements Runnable { @Override public void run() { // 模擬耗時任務 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("MyRunnable"); } }
執行上面的程式碼你將得到如下結果:
main start // 這裡會阻塞一段時間,才會列印下面的內容 MyCallable 執行緒:pool-1-thread-1 MyCallable main end
通過上面的程式碼我們驗證了 Callable 和 Runnable 的不同點,那麼 Callable 是怎麼實現的呢?首先,我們將 Callable 的實現類 MyCallable 傳遞給了 ExecutorService.submit() 而 ExecutorService 是一個介面,那麼其必將在它的實現類中覆寫 submit() 方法;跟進 :Executors.newSingleThreadExecutor()
(圖一)
我們發現 ExecutorService 的實現類是 FinalizableDelegatedExecutorService ,再跟進:
(圖二)
我們發現 FinalizableDelegatedExecutorService 中沒有 submit() 方法,那麼其必在父類中實現了該方法,在跟進父類:
(圖三)
從 DelegatedExecutorService 中我們可以看出其 submit() 方法的實現呼叫了 ExecutorService.submit(),是不是感覺又回到原點了,迴圈呼叫?當然不是這樣的,注意這裡的 e.submit 是 DelegatedExecutorService 的一個區域性變數 ExecutorService 的方法,而 e 又是通過構造方法賦值的。現在讓我們回到 DelegatedExecutorService 的子類 FinalizableDelegatedExecutorService 看看構造方法中傳遞的是什麼,從圖二和圖一我們可以看到,該構造方法中傳遞的 ExecutorService 實現類是 ThreadPoolExecutor 。終於找到正主了,讓我們跟進 ThreadPoolExecutor 是如何實現 submit() 的,跟進後你會發現 ThreadPoolExecutor 中沒有 submit() 方法,那我們只好再到父類中找了:
(圖四)
(圖五)
從 AbstractExecutorService 程式碼我們可以看出 Runnable 介面 和 Callable 介面的處理方式一樣都是將其轉換為 RunnableFuture 。而 RunnableFuture 是一個介面,那麼其必有實現類來完成這個轉換過程,讓我們分別跟進這兩個 newTaskFor 看看 Runnable 和 Callable 的例項都是怎麼被轉換的:
(圖六)
從程式碼中我們可以看到 newTaskForjia() 方法的返回值是 FutureTask 型別的,再次跟進:
(圖七)
FutureTask 的這兩個構造方法的作用是為 callable 和 state 賦值,到此 Callable 和 Runnable 例項的處理方式一樣了;不同的是 Runnable 的例項要傳遞給 Executors.callable 用於生成 Callable 的例項。下面我們看看這個轉化的過程:
(圖八)
RunnableAdapter 是 Callable 介面的一個實現類:
(圖九)
RunnableAdapter 覆寫了 Callable 的 call() 方法,並在執行 call() 時執行了 Runnable 的 run() 方法;這樣就做得到了 Runnable 和 Callable 的統一。
既然 Runnable 和 Callable 統一了,那麼我們再回頭看看執行緒的執行方法 圖五 的 execute(Runnable runnable) 是怎麼實現的。 什麼情況? execute 傳遞的是 Runnable 例項,而我們把 Runnable 和 Callable 統一成 Callable 了。是不是感覺很奇怪?注意 圖五 中 execute 傳遞的是 RunnableFuture 的實現類 FutureTask 的例項。而 RunnableFuture 實現了 Runnable 介面,並覆寫了 Runnable 的 run() 方法:
(圖十)
那麼,Runnable 的實現類 FutureTask 是怎麼實現 run() 方法的呢?
(圖十一)
run() 方法中呼叫了 Callable.call() , 而 run() 又是覆寫的 Runnable 的 run() 方法,到此就理解為什麼 圖五 的 execute() 方法可以傳 RunnableFuture 例項了: 先將 Runnable 或 Callable 的例項統一為 Callable 型別,再在執行 run() 方法時呼叫 Callable 的 call() 方法。
細心的讀者可能發現了文章開頭的 demo 中有句註釋(// 這裡會發生阻塞),那是什麼原因導致阻塞呢?讓我們跟進 Future 的實現類 FutureTask (由 圖五 可以得到) 看看其 get() 方法是怎麼實現的:
(圖十二)
繼續跟進:
(圖十三)
awaitDone 是一個死迴圈,只有當待執行的 Callable 或 Runnable 結束時,才會跳出,這樣就不難理解 圖十二 的邏輯了,當 Callable 或 Runnable 已經結束時,直接給出返回值,否則就阻塞在 awaitDone() 方法,所以才有註釋中那句註釋。
結語
本文到此已經大致把 Callable 實現非同步的原理講解清楚了,為了更方便的理解,在這裡補一張使用 Callable 的流程圖。
附錄
關於 Executor 是屬於執行緒池的內容,不是本文重點,只需要知道:執行緒池在使用中要接收 Runnable 例項即可。
本文摘自原文,如有侵權,請聯絡我!!!