實現多線程的方法是1種還是2種還是4種?

網上和書籍的各種說法:魚龍混雜

  • 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類是不推薦的,因爲它有以下一些缺點:

  1. 從代碼架構角度:具體的任務(run方法)應該和“創建和運行線程的機制(Thread類)”解耦,用runnable對象可以實現解耦。
  2. 使用繼承Thread的方式的話,那麼每次想創建一個新任務,只能新建一個獨立的線程,而這樣做的損耗會比較大(比如重頭開始創建一個線程,執行完畢以後再銷燬等。如果線程的實際工作內容,就是run方法裏面只是簡單的打印一行字的話,那麼可能線程的實際工作內容還不如損耗來的大)。如果使用Runnable和線程池,就可以大大減少這樣的損耗
  3. 繼承Thread類後,由於Java語言不支持雙繼承,這樣就無法再繼承其他的類,限制了可擴展性
  • 兩種方法的本質對比
    方法一和方法二,也就是“實現Runnable接口並傳入Thread類”和“繼承Thread類然後重寫run方法”在實現多線程的本質上並沒有區別,最終都是調用了start()方法來新建線程。這兩個方法的最主要區別在於run方法的內容來源
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
  1. 方法一:最終調用target.run()
  2. 方法二: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.學習編程知識的優質路徑

  • 宏觀上
    1. 並不是靠工作年限,有的人工作了5年技術卻還是隻懂皮毛。
    2. 要有強大的責任心,不放過任何bug,找到原因並去解決,這就是提高。
    3. 主動:永遠不會覺得自己的時間多餘,重構、優化、學習、總結等。
    4. 敢於承擔:雖然這個技術難題以前沒碰到過,但是在一定的瞭解調研後,敢於承擔技術難題,讓工作充滿挑戰,這一次次攻克難關的過程中,進步是飛快的。
    5. 關心產品,關心業務,而不只是寫代碼。
  • 微觀上
    1. 看經典書籍
    2. 看官方文檔
    3. 英文搜google和Stack Overflow
    4. 自己動手寫,實踐寫demo,嘗試用到項目裏
    5. 不理解的參考該領域多個書本,綜合判斷
    6. 學習開源項目,分析源碼

2.如何瞭解技術領域的最新動態

  • 高質量固定途徑:ohmyrss.com(信息篩選,爲我所用)
  • 訂閱技術網站的郵件:InfoQ(每週都看)
  • 公衆號不推薦作爲技術知識來源,質量無法保證

3.如何在業務開發總成長

  • 偏業務方向
  • 偏技術方向
  • 兩個25%理論

面試問題

1.有多少種實現線程的方法?思路有五點

  • 從不同的角度看,會有不同的答案
  • 典型答案是兩種
  • 我們看原理,兩種本質都是一樣的
  • 具體展開說其他方式
  • 結論

答案示例:

  1. 從不同的季度看,會有不同的答案。
  2. 典型答案是兩種,分別是實現Runnable接口和繼承Thread類,然後具體展開說。
  3. 但是,我們看原理,其實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不支持多繼承
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章