面試官問我:創(chuàng)建線程有幾種方式?我笑了
前言
多線程在面試中基本上已經(jīng)是必問項(xiàng)了,面試官通常會從簡單的問題開始發(fā)問,然后再一步一步的挖掘你的知識面。
比如,從線程是什么開始,線程和進(jìn)程的區(qū)別,創(chuàng)建線程有幾種方式,線程有幾種狀態(tài),等等。
接下來自然就會引出線程池,Lock,Synchronized,JUC的各種并發(fā)包。然后就會引出 AQS、CAS、JMM、JVM等偏底層原理,一環(huán)扣一環(huán)。
這一節(jié)我們不聊其他的,只說創(chuàng)建線程有幾種方式。
是不是感覺非常簡單,不就是那個啥啥那幾種么。
其實(shí)不然,只有我們給面試官解釋清楚了,并加上我們自己的理解,才能在面試中加分。
正文
一般來說我們比較常用的有以下四種方式,下面先介紹它們的使用方法。然后,再說面試中怎樣回答面試官的問題比較合適。
1、繼承 Thread 類
通過繼承 Thread 類,并重寫它的 run 方法,我們就可以創(chuàng)建一個線程。
首先定義一個類來繼承 Thread 類,重寫 run 方法。 然后創(chuàng)建這個子類對象,并調(diào)用 start 方法啟動線程。

2、實(shí)現(xiàn) Runnable 接口
通過實(shí)現(xiàn) Runnable ,并實(shí)現(xiàn) run 方法,也可以創(chuàng)建一個線程。
首先定義一個類實(shí)現(xiàn) Runnable 接口,并實(shí)現(xiàn) run 方法。 然后創(chuàng)建 Runnable 實(shí)現(xiàn)類對象,并把它作為 target 傳入 Thread 的構(gòu)造函數(shù)中 最后調(diào)用 start 方法啟動線程。

3、實(shí)現(xiàn) Callable 接口,并結(jié)合 Future 實(shí)現(xiàn)
首先定義一個 Callable 的實(shí)現(xiàn)類,并實(shí)現(xiàn) call 方法。call 方法是帶返回值的。 然后通過 FutureTask 的構(gòu)造方法,把這個 Callable 實(shí)現(xiàn)類傳進(jìn)去。 把 FutureTask 作為 Thread 類的 target ,創(chuàng)建 Thread 線程對象。 通過 FutureTask 的 get 方法獲取線程的執(zhí)行結(jié)果。

4、通過線程池創(chuàng)建線程
此處用 JDK 自帶的 Executors 來創(chuàng)建線程池對象。
首先,定一個 Runnable 的實(shí)現(xiàn)類,重寫 run 方法。 然后創(chuàng)建一個擁有固定線程數(shù)的線程池。 最后通過 ExecutorService 對象的 execute 方法傳入線程對象。

到底有幾種創(chuàng)建線程的方式?
那么問題來了,我這里舉例了四種創(chuàng)建線程的方式,是不是說明就是四種呢?
我們先看下 JDK 源碼中對 Thread 類的一段解釋,如下圖。

There are two ways to create a new thread of execution
翻譯:有兩種方式可以創(chuàng)建一個新的執(zhí)行線程
這里說的兩種方式就對應(yīng)我們介紹的前兩種方式。
但是,我們會發(fā)現(xiàn)這兩種方式,最終都會調(diào)用 Thread.start 方法,而 start 方法最終會調(diào)用 run 方法。
不同的是,在實(shí)現(xiàn) Runnable 接口的方式中,調(diào)用的是 Thread 本類的 run 方法。我們看下它的源碼,

這種方式,會把創(chuàng)建的 Runnable 實(shí)現(xiàn)類對象賦值給 target ,并運(yùn)行 target 的 run 方法。
再看繼承 Thread 類的方式,我們同樣需要調(diào)用 Thread 的 start 方法來啟動線程。由于子類重寫了 Thread 類的 run 方法,因此最終執(zhí)行的是這個子類的 run 方法。
所以,我們也可以這樣說。在本質(zhì)上,創(chuàng)建線程只有一種方式,就是構(gòu)造一個 Thread 類(其子類其實(shí)也可以認(rèn)為是一個 Thread 類)。
而構(gòu)造 Thread 類又有兩種方式,一種是繼承 Thread 類,一種是實(shí)現(xiàn) Runnable接口。其最終都會創(chuàng)建 Thread 類(或其子類)的對象。
再來看實(shí)現(xiàn) Callable ,結(jié)合 Future 和 FutureTask 的方式??梢园l(fā)現(xiàn),其最終也是通過 ?new Thread(task) 的方式構(gòu)造 Thread 類。
最后,在線程池中,我們其實(shí)是把創(chuàng)建和管理線程的任務(wù)都交給了線程池。而創(chuàng)建線程是通過線程工廠類 DefaultThreadFactory 來創(chuàng)建的(也可以自定義工廠類)。我們看下這個工廠類的具體實(shí)現(xiàn)。

它會給線程設(shè)置一些默認(rèn)值,如線程名稱,線程的優(yōu)先級,線程組,是否是守護(hù)線程等。最后還是通過 new Thread() 的方式來創(chuàng)建線程的。
因此,綜上所述。在回答這個問題的時候,我們可以說本質(zhì)上創(chuàng)建線程就只有一種方式,就是構(gòu)造一個 Thread 類。(此結(jié)論借鑒來源于 Java 并發(fā)編程 78 講 -- 徐隆曦)
個人想法
但是,在這里我想對這個結(jié)論稍微提出一些疑問(若有不同見解,文末可留言交流~)。。。
個人認(rèn)為,如果你要說有 1種、2種、3種、4種 其實(shí)也是可以的。重要的是,你要能說出你的依據(jù),講出它們各自的不同點(diǎn)和共同點(diǎn)。講得頭頭是道,讓面試官對你頻頻點(diǎn)頭。。
說只有構(gòu)造 Thread 類這一種創(chuàng)建線程方式,個人認(rèn)為還是有些牽強(qiáng)。因?yàn)?,無論你從任何手段出發(fā),想創(chuàng)建一個線程的話,最終肯定都是構(gòu)造 Thread 類。(包括以上幾種方式,甚至通過反射,最終不也是 newInstance 么)。
那么,如果按照這個邏輯的話,我就可以說,不管創(chuàng)建任何的對象(Object),都是只有一種方式,即構(gòu)造這個對象(Object) 類。這個結(jié)論似乎有些太過無聊了,因?yàn)檫@是一句非常正確的廢話。
以 ArrayList 為例,我問你創(chuàng)建 ArrayList 有幾種方式。你八成會為了炫耀自己知道的多,跟我說,
通過構(gòu)造方法, List list = new ArrayList();通過 Arrays.asList("a", "b");通過Java8提供的Stream API,如 List list = Stream.of("a", "b").collect(Collectors.toList());通過guava第三方j(luò)ar包, List list3 = Lists.newArrayList("a", "b");
等等,僅以上就列舉了四種?,F(xiàn)在,我告訴你創(chuàng)建 ArrayList 就只有一種方式,即構(gòu)造一個 ArrayList 類,你抓狂不。
這就如同,我問你從北京出發(fā)到上海去有幾種方式。
你說可以坐汽車、火車、坐動車、坐高鐵,坐飛機(jī)。
那不對啊,動車和高鐵都屬于火車啊,汽車和火車都屬于車,車和飛機(jī)都屬于交通工具。這樣就是只有一種方式了,即坐交通工具。
這也不對啊,我不坐交通工具也行啊,我走路過去不行么(我插眼傳送也可以啊,就你皮~)。
最后結(jié)論就是,只有一種方式,那就是你人到上海即可。這這這,這算什么結(jié)論。。。
所以個人認(rèn)為,說創(chuàng)建線程只有一種方式有些欠妥。
好好的一個技術(shù)文,差一點(diǎn)被我寫成議論文了。。。
這個仁者見仁智者見智吧。
最后,我們看一下我從網(wǎng)上看到的一個非常有意思的題目。
有趣的題目
問:一個類實(shí)現(xiàn)了 Runnable 接口就會執(zhí)行默認(rèn)的 run 方法,然后判斷 target 不為空,最后執(zhí)行在 Runnable接口中實(shí)現(xiàn)的 run 方法。而繼承 Thread 類,就會執(zhí)行重寫后的 run 方法。那么,現(xiàn)在我既繼承 Thread 類,又實(shí)現(xiàn) Runnable 接口,如下程序,應(yīng)該輸出什么結(jié)果呢?
public?class?TestThread?{
????public?static?void?main(String[]?args)?{
????????new?Thread(()->?System.out.println("runnable")){
????????????@Override
????????????public?void?run()?{
????????????????System.out.println("Thread?run");
????????????}
????????}.start();
????}
}
可能乍一看很懵逼,這是什么操作。
其實(shí),我們拆解一下以上代碼就會知道,這是一個繼承了 Thread 父類的子類對象,重寫了父類的 run 方法。然后,父對象 Thread 中,在構(gòu)造方法中傳入了一個 Runnable 接口的實(shí)現(xiàn)類,實(shí)現(xiàn)了 run 方法。
現(xiàn)在執(zhí)行了 start 方法,必然會先在子類中尋找 run 方法,找到了就會直接執(zhí)行,不會執(zhí)行父類的 run 方法了,因此結(jié)果為:Thread run 。
若假設(shè)子類沒有實(shí)現(xiàn) run 方法,那么就會去父類中尋找 run 方法,而父類的 run 方法會判斷是否有 Runnable傳過來(即判斷target是否為空),現(xiàn)在 target 不為空,因此就會執(zhí)行 target.run 方法,即打印結(jié)果:runnable。
所以,上邊的代碼看起來復(fù)雜,實(shí)則很簡單。透過現(xiàn)象看本質(zhì),我們就會發(fā)現(xiàn),它不過就是考察類的父子繼承關(guān)系,子類重寫了父類的方法就會優(yōu)先執(zhí)行子類重寫的方法。
和線程結(jié)合起來,如果對線程運(yùn)行機(jī)制不熟悉的,很可能就會被迷惑。
推薦閱讀:
喜歡我可以給我設(shè)為星標(biāo)哦


