網上和書籍的各種說法:魚龍混雜
- 1種觀點
- 2種觀點
- 4種觀點
- 其他觀點
實現多線程的官方正確方法:2種
- Oracle官網的文檔是如何寫的?
- 方法一:實現Runnable接口
- 方法二:繼承Thread類
方法一:實現Runnable接口
public class RunnableStyle implements Runnable{
public static void main(String[] args) {
Thread thread = new Thread(new RunnableStyle());
thread.start();
}
@Override
public void run() {
System.out.println("用Runnable方法實現線程");
}
}
方法二:繼承Thread類
public class ThreadStyle extends Thread{
@Override
public void run() {
System.out.println("用Thread類實現線程");
}
public static void main(String[] args) {
new ThreadStyle().start();
}
}
兩種方法的對比
- 方法一(實現Runnable接口)更好
繼承Thread類是不推薦的,因爲它有以下一些缺點:
- 從代碼架構角度:
具體的任務(run方法)應該和“創建和運行線程的機制(Thread類)”解耦
,用runnable對象可以實現解耦。 - 使用繼承Thread的方式的話,那麼每次想創建一個新任務,只能新建一個獨立的線程,而
這樣做的損耗會比較大
(比如重頭開始創建一個線程,執行完畢以後再銷燬等。如果線程的實際工作內容,就是run方法裏面只是簡單的打印一行字的話,那麼可能線程的實際工作內容還不如損耗來的大)。如果使用Runnable和線程池,就可以大大減少這樣的損耗
。 - 繼承Thread類後,由於Java語言不支持雙繼承,這樣就無法再繼承其他的類,
限制了可擴展性
。
- 兩種方法的本質對比
方法一和方法二,也就是“實現Runnable接口並傳入Thread類”和“繼承Thread類然後重寫run方法”在實現多線程的本質上並沒有區別
,最終都是調用了start()方法來新建線程。這兩個方法的最主要區別在於run方法的內容來源
:
@Override
public void run() {
if (target != null) {
target.run();
}
}
- 方法一:最終調用target.run()
- 方法二:run()整個都被重寫了
思考:同時用兩種方法會怎麼樣?
public class BothRunnableThread {
public static void main(String[] args) {
new Thread(
() -> System.out.println("我來自Runnable")
) {
@Override
public void run() {
System.out.println("我來自Thread");
}
}.start();
}
}
執行結果:
我來自Thread
從面向對象的思想去考慮
其實就是Runnable方式run()被實現,但是實現又被Thread方式run()方法給覆蓋了,所以只打印了Thread方式run()方法裏面的內容。
總結:最精準的描述
- 通常我們可以分爲兩類,Oracle也是這麼說的
- 準確的講,創建線程只有一種方式那就是構造Thread類,而實現線程的執行單元有兩種方式
- 方法一:實現Runnable接口的run方法,並把Runnable實例傳給Thread類
- 方法二:重寫Thread的run方法(繼承Thread類)
典型錯誤觀點
1.線程池創建線程也算是一種新建線程的方式
2.通過Callable和FutureTask創建線程,也算是一種新建線程的方式
3.無返回值是實現runnable接口,有返回值是實現callable接口,所以callable是新的實現線程的方法
4.定時器
5.匿名內部類
6.Lambda表達式
典型錯誤觀點總結
多線程的實現方式,在代碼中寫法千變萬化,但其本質萬變不離其宗
。
彩蛋
1.學習編程知識的優質路徑
- 宏觀上
- 並不是靠工作年限,有的人工作了5年技術卻還是隻懂皮毛。
- 要有強大的責任心,不放過任何bug,找到原因並去解決,這就是提高。
- 主動:永遠不會覺得自己的時間多餘,重構、優化、學習、總結等。
- 敢於承擔:雖然這個技術難題以前沒碰到過,但是在一定的瞭解調研後,敢於承擔技術難題,讓工作充滿挑戰,這一次次攻克難關的過程中,進步是飛快的。
- 關心產品,關心業務,而不只是寫代碼。
- 微觀上
- 看經典書籍
- 看官方文檔
- 英文搜google和Stack Overflow
- 自己動手寫,實踐寫demo,嘗試用到項目裏
- 不理解的參考該領域多個書本,綜合判斷
- 學習開源項目,分析源碼
2.如何瞭解技術領域的最新動態
- 高質量固定途徑:ohmyrss.com(信息篩選,爲我所用)
- 訂閱技術網站的郵件:InfoQ(每週都看)
- 公衆號不推薦作爲技術知識來源,質量無法保證
3.如何在業務開發總成長
- 偏業務方向
- 偏技術方向
- 兩個25%理論
面試問題
1.有多少種實現線程的方法?思路有五點
- 從不同的角度看,會有不同的答案
- 典型答案是兩種
- 我們看原理,兩種本質都是一樣的
- 具體展開說其他方式
- 結論
答案示例:
- 從不同的季度看,會有不同的答案。
- 典型答案是兩種,分別是實現Runnable接口和繼承Thread類,然後具體展開說。
- 但是,我們看原理,其實Thread實現了Runnable接口,並且看Thread類的run方法,會發現其實那兩種本質都是一樣的,run方法的代碼如下:
@Override
public void run() {
if (target != null) {
target.run();
}
}
方法一和方法二,也就是“繼承Thread類然後重寫run”和“實現Runnable接口並傳入Thread類”在實現多線程的本質上,並沒有區別,都是最終調用start方法來新建線程。這兩個方法最主要的區別在於run方法的內容來源:
方法一:最終調用target.run()
方法二:run()整個都被重寫
4. 然後具體展開說其他方式,還有其他的實現線程的方法,例如線程池等,它們也能新建線程,但是細看源碼,從沒逃出過本質,也就是實現Runnable接口和繼承Thread類。
5. 結論:我們只能通過新建Thread類這一方式來創建線程,但是類裏面的run方法有兩種方式來實現,第一種是重寫run方法,第二種是實現Runnable接口的run方法,然後把該runnable實例傳給Thread類。除此之外,從表面上看,線程池、定時器等工具類也可以創建線程,但是它們的本質都逃不過剛纔所說的範圍。
2.實現Runnable接口和繼承Thread類哪種方式更好?
- 從代碼架構角度
- 新建線程的損耗
- Java不支持多繼承