前言
多執行緒在面試中基本上已經是必問項了,面試官通常會從簡單的問題開始發問,然后再一步一步的挖掘你的知識面,
比如,從執行緒是什么開始,執行緒和行程的區別,創建執行緒有幾種方式,執行緒有幾種狀態,等等,
接下來自然就會引出執行緒池,Lock,Synchronized,JUC的各種并發包,然后就會引出 AQS、CAS、JMM、JVM等偏底層原理,一環扣一環,
這一節我們不聊其他的,只說創建執行緒有幾種方式,
是不是感覺非常簡單,不就是那個啥啥那幾種么,
其實不然,只有我們給面試官解釋清楚了,并加上我們自己的理解,才能在面試中加分,
正文
一般來說我們比較常用的有以下四種方式,下面先介紹它們的使用方法,然后,再說面試中怎樣回答面試官的問題比較合適,
1、繼承 Thread 類
通過繼承 Thread 類,并重寫它的 run 方法,我們就可以創建一個執行緒,
- 首先定義一個類來繼承 Thread 類,重寫 run 方法,
- 然后創建這個子類物件,并呼叫 start 方法啟動執行緒,

2、實作 Runnable 介面
通過實作 Runnable ,并實作 run 方法,也可以創建一個執行緒,
- 首先定義一個類實作 Runnable 介面,并實作 run 方法,
- 然后創建 Runnable 實作類物件,并把它作為 target 傳入 Thread 的建構式中
- 最后呼叫 start 方法啟動執行緒,

3、實作 Callable 介面,并結合 Future 實作
- 首先定義一個 Callable 的實作類,并實作 call 方法,call 方法是帶回傳值的,
- 然后通過 FutureTask 的構造方法,把這個 Callable 實作類傳進去,
- 把 FutureTask 作為 Thread 類的 target ,創建 Thread 執行緒物件,
- 通過 FutureTask 的 get 方法獲取執行緒的執行結果,

4、通過執行緒池創建執行緒
此處用 JDK 自帶的 Executors 來創建執行緒池物件,
- 首先,定一個 Runnable 的實作類,重寫 run 方法,
- 然后創建一個擁有固定執行緒數的執行緒池,
- 最后通過 ExecutorService 物件的 execute 方法傳入執行緒物件,

到底有幾種創建執行緒的方式?
那么問題來了,我這里舉例了四種創建執行緒的方式,是不是說明就是四種呢?
我們先看下 JDK 原始碼中對 Thread 類的一段解釋,如下圖,

There are two ways to create a new thread of execution
翻譯: 有兩種方式可以創建一個新的執行執行緒
這里說的兩種方式就對應我們介紹的前兩種方式,
但是,我們會發現這兩種方式,最終都會呼叫 Thread.start 方法,而 start 方法最侄訓呼叫 run 方法,
不同的是,在實作 Runnable 介面的方式中,呼叫的是 Thread 本類的 run 方法,我們看下它的原始碼,

這種方式,會把創建的 Runnable 實作類物件賦值給 target ,并運行 target 的 run 方法,
再看繼承 Thread 類的方式,我們同樣需要呼叫 Thread 的 start 方法來啟動執行緒,由于子類重寫了 Thread 類的 run 方法,因此最終執行的是這個子類的 run 方法,
所以,我們也可以這樣說,在本質上,創建執行緒只有一種方式,就是構造一個 Thread 類(其子類其實也可以認為是一個 Thread 類),
而構造 Thread 類又有兩種方式,一種是繼承 Thread 類,一種是實作 Runnable介面,其最終都會創建 Thread 類(或其子類)的物件,
再來看實作 Callable ,結合 Future 和 FutureTask 的方式,可以發現,其最終也是通過 new Thread(task) 的方式構造 Thread 類,
最后,在執行緒池中,我們其實是把創建和管理執行緒的任務都交給了執行緒池,而創建執行緒是通過執行緒工廠類 DefaultThreadFactory 來創建的(也可以自定義工廠類),我們看下這個工廠類的具體實作,

它會給執行緒設定一些默認值,如執行緒名稱,執行緒的優先級,執行緒組,是否是守護執行緒等,最后還是通過 new Thread() 的方式來創建執行緒的,
因此,綜上所述,在回答這個問題的時候,我們可以說本質上創建執行緒就只有一種方式,就是構造一個 Thread 類,(此結論借鑒來源于 Java 并發編程 78 講 -- 徐隆曦)
個人想法
但是,在這里我想對這個結論稍微提出一些疑問(若有不同見解,文末可留言交流~),,,
個人認為,如果你要說有 1種、2種、3種、4種 其實也是可以的,重要的是,你要能說出你的依據,講出它們各自的不同點和共同點,講得頭頭是道,讓面試官對你頻頻點頭,,
說只有構造 Thread 類這一種創建執行緒方式,個人認為還是有些牽強,因為,無論你從任何手段出發,想創建一個執行緒的話,最終肯定都是構造 Thread 類,(包括以上幾種方式,甚至通過反射,最終不也是 newInstance 么),
那么,如果按照這個邏輯的話,我就可以說,不管創建任何的物件(Object),都是只有一種方式,即構造這個物件(Object) 類,這個結論似乎有些太過無聊了,因為這是一句非常正確的廢話,
以 ArrayList 為例,我問你創建 ArrayList 有幾種方式,你八成會為了炫耀自己知道的多,跟我說,
- 通過構造方法,
List list = new ArrayList(); - 通過
Arrays.asList("a", "b"); - 通過Java8提供的Stream API,如
List list = Stream.of("a", "b").collect(Collectors.toList()); - 通過guava第三方jar包,
List list3 = Lists.newArrayList("a", "b");
等等,僅以上就列舉了四種,現在,我告訴你創建 ArrayList 就只有一種方式,即構造一個 ArrayList 類,你抓狂不,
這就如同,我問你從北京出發到上海去有幾種方式,
你說可以坐汽車、火車、坐動車、坐高鐵,坐飛機,
那不對啊,動車和高鐵都屬于火車啊,汽車和火車都屬于車,車和飛機都屬于交通工具,這樣就是只有一種方式了,即坐交通工具,
這也不對啊,我不坐交通工具也行啊,我走路過去不行么(我插眼傳送也可以啊,就你皮~),
最后結論就是,只有一種方式,那就是你人到上海即可,這這這,這算什么結論,,,
所以個人認為,說創建執行緒只有一種方式有些欠妥,
好好的一個技術文,差一點被我寫成議論文了,,,
這個仁者見仁智者見智吧,
最后,我們看一下我從網上看到的一個非常有意思的題目,
但是,我們知道 HashMap 本身就有四個不同引數的建構式,如下圖,
有四種方式可以創建 HashMap 物件,除此之外,還可以通過
有趣的題目
問:一個類實作了 Runnable 介面就會執行默認的 run 方法,然后判斷 target 不為空,最后執行在 Runnable介面中實作的 run 方法,而繼承 Thread 類,就會執行重寫后的 run 方法,那么,現在我既繼承 Thread 類,又實作 Runnable 介面,如下程式,應該輸出什么結果呢?
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();
}
}
可能乍一看很懵逼,這是什么操作,
其實,我們拆解一下以上代碼就會知道,這是一個繼承了 Thread 父類的子類物件,重寫了父類的 run 方法,然后,父物件 Thread 中,在構造方法中傳入了一個 Runnable 介面的實作類,實作了 run 方法,
現在執行了 start 方法,必然會先在子類中尋找 run 方法,找到了就會直接執行,不會執行父類的 run 方法了,因此結果為:Thread run ,
若假設子類沒有實作 run 方法,那么就會去父類中尋找 run 方法,而父類的 run 方法會判斷是否有 Runnable傳過來(即判斷target是否為空),現在 target 不為空,因此就會執行 target.run 方法,即列印結果: runnable,
所以,上邊的代碼看起來復雜,實則很簡單,透過現象看本質,我們就會發現,它不過就是考察類的父子繼承關系,子類重寫了父類的方法就會優先執行子類重寫的方法,
和執行緒結合起來,如果對執行緒運行機制不熟悉的,很可能就會被迷惑,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/188891.html
標籤:其他
