面試官:Callable與Runnable的區(qū)別你知道嗎?
你知道的越多,不知道的就越多,業(yè)余的像一棵小草!
你來(lái),我們一起精進(jìn)!你不來(lái),我和你的競(jìng)爭(zhēng)對(duì)手一起精進(jìn)!
編輯:業(yè)余草
https://juejin.cn/post/7061975326393368583
推薦:https://www.xttblog.com/?p=5313
昨天情人節(jié),今天元宵節(jié)。你們都是怎么過(guò)的?
?JDK版本:JDK11
?
1. 背景
在平時(shí)的開(kāi)發(fā)過(guò)程中線(xiàn)程肯定用不少,線(xiàn)程啟動(dòng)執(zhí)行需要實(shí)現(xiàn) 「Runnable」 類(lèi):
public?class?ThreadTest?{
????public?static?void?main(String[]?args)?{
????????new?Thread(new?Runnable()?{
????????????@Override
????????????public?void?run()?{
????????????????System.out.println(111);
????????????}
????????},?"Thread-mxsm").start();
????}
}
是自己新建一個(gè)線(xiàn)程對(duì)象,然后執(zhí)行「Runnable」 執(zhí)行完成線(xiàn)程結(jié)束。
除了這樣的還有使用到線(xiàn)程池,如下:
public?class?ThreadTest?{
????public?static?void?main(String[]?args)?{
???????ExecutorService?executorService?=?Executors.newFixedThreadPool(2);
????????executorService.execute(new?Runnable()?{
????????????@Override
????????????public?void?run()?{
????????????????System.out.println(111);
????????????}
????????});
????}
}
但是在線(xiàn)程池來(lái)執(zhí)行提交任務(wù)的時(shí)候,你可能注意到了這樣情況如下圖:

在標(biāo)號(hào)1的位置,你會(huì)發(fā)現(xiàn)竟然還可以提交一個(gè) 「Callable」 到線(xiàn)程池進(jìn)行執(zhí)行。
「問(wèn)題來(lái)了:」
「Runnable和Callable有什么關(guān)系,為什么線(xiàn)程池可以提交Callable?!?/strong> 「從單個(gè)線(xiàn)程來(lái)看,線(xiàn)程Thead只能執(zhí)行Runnable接口的實(shí)現(xiàn),但是線(xiàn)程池為什么就可以執(zhí)行Callable」 「Callable如何經(jīng)過(guò)包裝變成Runnable給線(xiàn)程調(diào)用」

2. Callable與Runnable的關(guān)聯(lián)分析
從上面的列出的三個(gè)方面來(lái)分析兩者之間的關(guān)聯(lián)。
2.1 Callable與Runnable源碼分析
「Runnable」 平時(shí)在開(kāi)發(fā)的時(shí)候經(jīng)常用,所以大家也比較熟悉:
@FunctionalInterface
public?interface?Runnable?{
????public?abstract?void?run();
}
只有一個(gè)方法 「run」 。在源碼中的注釋中翻譯總結(jié)一下就是:「任何需要由Thread執(zhí)行的類(lèi)都需要實(shí)現(xiàn)Runnable」。
?Tips: 所以Callable應(yīng)該是巧妙的轉(zhuǎn)換成了Runnable
?
「Callable」 用的不多,看一下源碼:
@FunctionalInterface
public?interface?Callable<V>?{
????V?call()?throws?Exception;
}
「Callable」 也只有一個(gè)方法 「call」 但是方法有返回值。
「從源碼上面可以看出來(lái) Callable 和 Runnable 沒(méi)有任何繼承關(guān)系,Runnable的方法沒(méi)有返回值,而Callable的方法有返回值。」
2.2 從線(xiàn)程池看Callable如何包裝成Runnable
JDK在Runnable的注釋上有明確的說(shuō)明:任何需要由Thread執(zhí)行的類(lèi)都需要實(shí)現(xiàn)Runnable。所以我們可以推斷出來(lái)Callable用了某種方式包裝成了Runnable。
通過(guò) 「ExecutorService#submit」 方法提交 Callable 來(lái)分析:

AbstractExecutorService#submit方法中 newTaskFor 方法將 Callable轉(zhuǎn)換成了 RunnableFuture。

通過(guò) 「newTaskFor」 方法可以發(fā)現(xiàn),最終是創(chuàng)建了一個(gè) 「FutureTask」 對(duì)象,「Callable」 作為構(gòu)造函數(shù)的參數(shù)。那么看一下「FutureTask」 :
public?class?FutureTask<V>?implements?RunnableFuture<V>?{
????/**?The?underlying?callable;?nulled?out?after?running?*/
????private?Callable?callable;
????/**?The?result?to?return?or?exception?to?throw?from?get()?*/
????private?Object?outcome;?//?non-volatile,?protected?by?state?reads/writes
????/**?The?thread?running?the?callable;?CASed?during?run()?*/
????private?volatile?Thread?runner;
????/**?Treiber?stack?of?waiting?threads?*/
????private?volatile?WaitNode?waiters;
????
????public?FutureTask(Callable?callable) ?{
????????if?(callable?==?null)
????????????throw?new?NullPointerException();
????????this.callable?=?callable;
????????this.state?=?NEW;???????//?ensure?visibility?of?callable
????}
?//省略部分代碼
}
Callable作為了FutureTask的一個(gè)屬性值。之前說(shuō)過(guò),要想被Thread執(zhí)行必須實(shí)現(xiàn)Runnable。那么我們看一下 「FutureTask」 的實(shí)現(xiàn)類(lèi) 「RunnableFuture」 :

「RunnableFuture」 繼承了 「Runnable」
換句話(huà)說(shuō):「FutureTask 實(shí)現(xiàn)了 Runnable」

「FutureTask#run 內(nèi)部就調(diào)用了Callable的call方法」
然后由線(xiàn)程池中的Thread去執(zhí)行FutureTask(也就是Runnable的實(shí)現(xiàn)的實(shí)例)。上面類(lèi)的繼承關(guān)系:

「Callable轉(zhuǎn)換成Runnable的流程」:
「開(kāi)發(fā)者實(shí)現(xiàn)Callable接口」 「實(shí)例化Callable,然后提交到線(xiàn)程池」 「以Callable為構(gòu)造函數(shù)創(chuàng)建FutureTask」 「最終將FutureTask提交給線(xiàn)程池的線(xiàn)程進(jìn)行執(zhí)行」
?Tips: 在提交Runnable的實(shí)現(xiàn)到線(xiàn)程池執(zhí)行的時(shí)候,如果需要獲取到返回值,會(huì)將 Runnable的實(shí)例,通過(guò)RunnableAdapter適配器適配成Callable。
?
?private?static?final?class?RunnableAdapter<T>?implements?Callable<T>?{
?????private?final?Runnable?task;
?????private?final?T?result;
?????RunnableAdapter(Runnable?task,?T?result)?{
?????????this.task?=?task;
?????????this.result?=?result;
?????}
?????public?T?call()?{
?????????task.run();
?????????return?result;
?????}
?????public?String?toString()?{
?????????return?super.toString()?+?"[Wrapped?task?=?"?+?task?+?"]";
?????}
?}
2.3 線(xiàn)程池如何執(zhí)行Callable有什么特點(diǎn)
將Callable包裝成Runnable后,線(xiàn)程池的執(zhí)行和執(zhí)行Runnable一樣,Callable的特點(diǎn)就是可以獲取到返回值。如果執(zhí)行的邏輯不關(guān)心返回值就可以直接用Runnable來(lái)。但是如果需要涉及到獲取到業(yè)務(wù)邏輯中的返回值那么就使用Callable來(lái)提交到線(xiàn)程池中。
3. 總結(jié)
「Runnable和Callable兩者沒(méi)有繼承關(guān)系,Callable通過(guò)FutureTask包裝成Runnable。」 「線(xiàn)程池執(zhí)行任務(wù)的時(shí)候,如果關(guān)系返回值就用Callable,不關(guān)心返回值用Runnable?!?/strong> 「Runnable如果也需要返回值,線(xiàn)程池內(nèi)部是通過(guò)RunnableAdapter適配器來(lái)適配成Callable」
