單純爲了實用,創建線程的幾種方法

寫在前面

如果覺得有所收穫,記得的點個關注和點個贊,感謝支持。
原始的創建線程的方法有三種,分別是:

  • 繼承 Thread 類
  • 實現 Runnable 接口
  • 實現 Callable 接口

這篇博文就單純的針對這三種接口,講解如何使用,旨在使用,並不深入講解各種原理乃至講解線程相關,嘿嘿嘿。

繼承 Thread 類

這是一種最爲原始的方式,繼承 Thread 類,重寫 run 方法以實現線程功能。

public class HelloThread extends Thread {
    @Override
    public void run(){
        System.out.println("hello.");
    }
}

上面的代碼是指,我創建了一個名爲 HelloThread 的類,該類由於繼承 Thread 類因而是一個線程類,它重寫了父類的 run 方法,打印了一句話:【hello.】。使用時也異常簡單:

Thread thread = new HelloThread();
thread.start();

首先實例化一個線程出來,然後啓動線程,當線程啓動之後,控制檯上打印了一句話:【hello.】。一般情況下不要使用這種方式來創建線程,因爲通過繼承來實現實在是太臃腫了,很多場景下我們只需要讓線程跑起來,實現某個功能(即重寫 run 方法),但是繼承會實現 Thread 類的全部信息,性能消耗太大。而且 Java 是單繼承的,繼承了 Thread 類就不能繼承其他類了。

實現 Runnable 接口

Runnable 接口是一切線程創建的根源,其實上面【繼承 Thread 類】的途徑,也是間接使用了本途徑來創建線程的。比較傳統的實現 Runnable 接口的方式是,創建一個類,該類 implements Runnable 來實現 Runnable 接口。

public class HelloRunnable implements Runnable {
    // ...
}

但是創建一個類,未免太大張旗鼓了些,還要新建一個類,設置好類名,實現接口,之後再實例化,興師動衆。其實實例化對象並不需要創建一個類出來,實現接口就行,用匿名內部類。

Runnable helloRunnable = new Runnable() {
    // ...
};

此外,Runnable 接口是一個函數式接口,只定義了 run 方法,可以使用 lambda 表達式的方式來實例化,那就更簡單了。

Runnable HelloRunnable = () -> {
    // ...
};

上面三種實現,都只是寫了外殼,裏面沒有寫具體的實現過程,具體的實現是要重寫 Runnable 接口的 run 方法的。我寫了三種實現 Runnable 接口的代碼,第一種最容易懂,後面兩種如果有困惑,看一看 lambda 表達式就能理解了。實現了 Runnable 接口之後,把它作爲參數,放進 Thread 的構造方法裏就可以了。

Thread thread = new Thread(runnable);
thread.start();

這樣就可以了。要不再完整地走一遍?

// 實現 Runnable 接口,重寫 run 方法 (這裏使用匿名內部類的方式,即上面的第二種)
Runnable runnable = new Runnable() {
    @Override
    public void run() {
        System.out.println("by runnable");
    }
};

// 創建線程並開啓
Thread thread = new Thread(runnable);
thread.start();

// 控制檯上會打印出這樣一句話:by runnable

實現 Callable 接口

以上兩種方式,都沒有任何的返回值,線程執行動作,執行完就結束了,無聲無息。實現 Callable 接口的目的,就是爲了讓線程執行完之後,能返回信息。簡單對比一下,Runnable 接口和 Callable 接口,在代碼上的區別:

// 實現 Runnable 接口
Runnable runnable = new Runnable() {
    @Override
    public void run() {
        // ...
    }
};

// 實現 Callable 接口
Callable callable = new Callable() {
    @Override
    public Object call() throws Exception {
        // ...
        return null;
    }
};

你會發現,實現兩個接口都只需要重寫一個方法:

  • 實現 Runnable 接口需要重寫【沒有返回值】的 run 方法
  • 實現 Callable 接口需要實現【返回一個對象】的 call 方法。

其他的地方,在用法上彷彿沒有什麼不同。實際上,Callable 接口還支持泛型,你可以指定返回值的數據類型:

// 指定返回 String 類型
Callable<String> callable = new Callable<String>() {
    @Override
    public String call() throws Exception {
        // ...
        return null;
    }
};

傳統的線程設計,是沒有返回值的概念的,因此沒辦法用線程類來獲得返回值。JUC 包設計了一個新的接口:Future,來接收線程的返回值(和其他的功能)。Future 類是一個接口,無法直接實例化,因此又設計了一個名爲 FutureTask 的類,該類實現了Future 接口和 Runnable 接口,打通了【線程功能】和【返回值功能】。

FutureTask futureTask = new FutureTask(callable);
Thread thread = new Thread(futureTask);

上面這兩行代碼,是將剛纔寫好的 callable 對象,放進 futureTask 中,輾轉放進線程中。你可以感受到,FutureTask 類是一箇中介,它也支持泛型(不過上面這兩行代碼沒寫泛型)。FutureTask 類有一個 get 方法,用於獲取 callable 的返回值。

Object returnStr = futureTask.get();

(如果指定了 FutureTask 的泛型,上面還可以更確切地指定數據類型,例如把上面代碼的 Object 改成 String

這個 get 方法需要處理兩類異常:InterruptedExceptionExecutionException

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章