十分鐘搞定Java多線程-這樣回答「Thread和Runnable的區別」很酷,面試官會對你另眼相看

Java提供了多線程來並行執行任務(代碼),你須要線程來並行運行多個任務,例如,在後臺下載一個文件,並在前端顯示進度條。有兩種方法能夠在Java中建立線程,一是繼承java.lang.Thread,二是實現java.lang.Runnable接口。面試官也很是喜歡問這個問題,建立Java線程繼承Thread類和實現Runnable接口有什麼區別以及如何選擇,小問題蘊含大道理。本文中,咱們將探討Thread和Runnable的區別,以及如何回答這個問題。前端

你可能這樣回答:java

若是咱們繼承Thread類建立線程,咱們將失去擴展另外一個類的機會, 可是,若是是實現Runnable接口,咱們就還有繼承另外一個類的機會,由於Java支持類的單繼承,不支持多繼承,但支持實現多個接口。面試

沒錯!多線程

儘管上述回答是正確的,但它並非一個使人印象深入的答案。若是你是一個有經驗的coder,放眼四海皆准的回答,讓面試官對你評價,就是你面試前刷題庫了。雖然咱們確實是刷題庫了,刷題庫確實也是有效的學習方式,可是想要脫穎而出, 爲什麼不刷更加漂亮得答案。面試官認爲你有很是清晰本身的看法,看出你的回答有明顯的亮點,給你更高評分並且可能很快會轉換到其餘問題。ide

1、繼承Thread和實現Runnable接口的區別

繼承Thread和實現Runnable接口的區別從如下四點進行展開描述:學習

  1. 線程對象
  2. 線程的內存消耗
  3. 線程在多重繼承的狀況下
  4. 重寫線程方法

1)線程對象

當咱們經過繼承Thread建立線程時,每一個線程都會建立線程類的惟一對象。例如,若是您建立了5個線程,那麼內存中就有5個不一樣的對象。spa

當咱們實現Runnable接口時,咱們只建立一個線程類對象,咱們傳遞給多線程的是同一個對象。也就說,全部線程共享同一個線程類對象。線程

咱們經過一個線程示例來理解它們。code

a)繼承Thread建立線程

在下面的Java代碼示例中,咱們在main()方法中建立了兩個線程t1和t2, class Task是線程類。對象

注意,對於咱們建立的每一個線程,咱們必須在內存中爲其建立不一樣的對象,例如:

// thread 1
Task t1 = new Task();
// thread 2
Task t2 = new Task();

下面是完整代碼示例:

public class Thread4 {

    public static void main(String[] args) {
        // thread 1
        Task t1 = new Task();
        t1.start();
        // thread 2
        Task t2 = new Task();
        t2.start();

    }

}

//task thread class
class Task extends Thread {

    public void run() {
        System.out.println("Thread name is : " + currentThread().getName());
    }
}

輸出結果:

Thread name is : Thread-1
Thread name is : Thread-0

b)實現Runnable接口時建立線程

下面的示例與上面的相同,可是使用的是runnable接口。注意,咱們只建立了線程類任務的一個對象,例如:Task r = new Task(),並將相同的對象r傳遞給兩個不一樣的線程t1和t2。換句話說,t1和t2共享線程類「Task」的相同對象。

public class Thread5 {

    public static void main(String[] args) {

        //建立Task線程類
        Task r = new Task();

        // 建立 thread 1
        Thread t1 = new Thread(r);
        t1.start();

        Thread t2 = new Thread(r);
        t2.start();

    }

}

//實現Runnable接口
class Task implements Runnable {

    public void run() {
        System.out.println("Runnable thread is running:");
    }
    
}

輸出結果:

Runnable thread is running
Runnable thread is running

2)線程的內存消耗

從上面的代碼示例咱們已經看到,若是是繼承Thread類建立多線程,那麼咱們必須建立線程類「Task」的多個對象。可是,在使用Runnable接口的例子中,只建立了一個對象,而且將線程類「Task」一個對象共享給多個線程。

所以,若是線程類比較重量級(佔用大量內存),那麼最好使用Runnable接口。

不過,若是你不瞭解如何使用Runnable Interface來節省內存,請閱讀文末給出的詳細說明。

3)線程在多重繼承的狀況下

前面咱們提過,若是繼承Thread類建立線程,咱們就失去了繼承另外一個類的機會,可是若是咱們實現Runnable接口,咱們就能夠保留繼承另外一個類的機會。Java不支持使用類的多繼承,可是支持實現多接口。

4)重寫線程方法

Sometimes we need to override the methods of Thread class. For example, start () method to write our own code inside that, then Extending thread class is option, whereas, it is not possible with Runnable interface.

有時咱們須要重寫Thread類的方法,例如,重寫start()方法並在其中加入咱們本身的代碼,這時咱們須要繼承Thread類,由於使用Runnable接口是不能實現該需求的。

爲何須要在Java線程中重寫start()方法?由於咱們可能想要線程啓動以前初始化一些東西,可能要調用一些方法,初始一些任務,而後再開始一個新的線程。在這種狀況下,咱們必須重寫start()方法。

2、總結

1)繼承Thread類與實現Runnable接口的區別。

  • 實現Runnable接口,還能夠繼承另外一個類, 而繼承Thread類建立線程則不能再繼承別的類了。
  • 在有建立多個線程需求的狀況下,使用Runnable接口能夠節省內存,由於 咱們沒必要建立線程類的多個對象。
  • 經過繼承Thread類能夠實現方法重寫功能,而Runnable接口沒法實現。

2)咱們如何選擇

首選 Runnable 接口:

  • 當不想失去繼承其餘類的機會時。
  • 當不須要重寫Thread類的方法時。
  • 執行多線程任務消耗比較多的內存時。

3)繼承Thread類和實現Runnable接口的內存消耗計算

有的人可能一時半會不能理解在上面的例子中咱們是如何節省內存的,由於在這兩種狀況下建立的對象總數都是3。

舉個簡單的例子來講明一下。

嚴格來講,咱們不能直接根據程序中對象計數的總數來統計內存消耗。

在這兩種狀況下(任務+線程類),對象總數是相同的。可是,咱們須要考慮類的大小。

舉個例子:

假設「 Thread」類和「 Task」類的兩個類的大小。

Thread類大小: 10 K

Task類大小: 100 K, 由於它可能包含50個變量int, float和double等。

假設咱們在程序中建立了20個線程。而後在建立20個線程時,

若是繼承Thread類–你建立20個任務類對象,結果大小 =  20 * 100k = 2000k

在實現Runnable接口的狀況下:建立1個Task類對象和20個Thread類對象,結果大小爲1 * 100k + 20 * 10K = 100k + 200k = 300K。

所以,對於相同的需求,在擴展Thread類時,總內存大小爲2000k,而在實現Runnable接口時,總內存大小僅僅爲300K。

所以,得出結論:若是實現Runnable接口,會更節省內存 !

相關文章
相關標籤/搜索