Java 并發(fā)編程:本質(zhì)上只有一種創(chuàng)建線程的方法
在上一篇文章中,我們學(xué)習(xí)了操作系統(tǒng)中線程的基本概念。那么在 Java 中,我們?nèi)绾蝿?chuàng)建和使用線程呢?首先請思考一個問題。創(chuàng)建線程有多少種方法呢?大多數(shù)人會說有 2 種、3 種或 4 種。很少有人會說只有 1 種。讓我們看看他們實際指的是什么。最常見的答案是兩種創(chuàng)建線程的方法。讓我們先看看這兩種線程創(chuàng)建方法的代碼。

Thread 類和 Runnable 接口
(1) 繼承 Thread 類:第一種是繼承 Thread 類并重寫 run() 方法:
class SayHelloThread extends Thread {
    public void run() {
        System.out.println("hello world");
    }
}
public class ThreadJavaApp {
    public static void main(String[] args) {
        SayHelloThread sayHelloThread = new SayHelloThread();
        sayHelloThread.start();
    }
}只有在主線程中創(chuàng)建 MyThread 的實例并調(diào)用 start() 方法,線程才會啟動。
(2) 實現(xiàn) Runnable 接口:接下來看看 Runnable 接口:
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}實現(xiàn) Runnable 接口的 run() 方法,在這個方法里可以定義相應(yīng)的業(yè)務(wù)邏輯,不過我們還是需要通過 Thread 類來啟動線程。
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Hello Runnable");
    }
}
public class RunnableDemo {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        new Thread(myRunnable).start();
    }
}從 Runnable 接口的定義中可以看出,Runnable 是一個函數(shù)式接口(JDK 1.8 及以上),這意味著我們可以使用 Java 8 的函數(shù)式編程來簡化代碼:
public class RunnableDemo {
    public static void main(String[] args) {
        // Java 8 函數(shù)式編程,可以省略 MyThread 類的定義
        new Thread(() -> {
            System.out.println("Lambda 表達式實現(xiàn) Runnable");
        }).start();
    }
}Callable、Future 和 FutureTask
一般來說,我們使用 Runnable 和 Thread 來創(chuàng)建一個新線程。然而,它們有一個缺點,即 run 方法沒有返回值。有時我們希望啟動一個線程來執(zhí)行任務(wù),并且在任務(wù)完成后有一個返回值。JDK 為我們提供了 Callable 接口來解決這個問題。
(1) Callable 接口:Callable 與 Runnable 類似,它也是一個只有一個抽象方法的函數(shù)式接口。不同之處在于 Callable 提供的方法有返回值并支持泛型。
@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}那么 Callable 通常如何使用呢?它是否與 Runnable 接口一樣,傳入 Thread 類呢?讓我們查看 JDK8 的 Java API,發(fā)現(xiàn)沒有使用 Callable 作為參數(shù)的構(gòu)造方法。

(2) Future 接口和 FutureTask 類:實際上它提供了 FutureTask 類來完成有返回值的異步計算。FutureTask 實現(xiàn)了 RunnableFuture 接口,而 RunnableFuture 接口同時繼承了 Runnable 接口和 Future 接口,因此可以傳入 Thread(Runable target)。
public interface RunnableFuture<V> extends Runnable, Future<V> {
  /**
     * Sets this Future to the result of its computation,
     * unless it has been cancelled.
     */
    void run();
}Future 接口只有幾個簡單的方法:
public abstract interface Future<V> {
    public abstract boolean cancel(boolean paramBoolean);
    public abstract boolean isCancelled();
    public abstract boolean isDone();
    public abstract V get() throws InterruptedException, ExecutionException;
    public abstract V get(long paramLong, TimeUnit paramTimeUnit)
            throws InterruptedException, ExecutionException, TimeoutException;
}每個方法的功能如下:
- get():等待計算完成并返回結(jié)果。
 - get(long paramLong, TimeUnit paramTimeUnit):等待設(shè)定的時間。如果在設(shè)定時間內(nèi)計算完成,則返回結(jié)果,否則拋出 TimeoutException。
 - isDone:如果任務(wù)完成,則返回 true。完成可能是由于正常終止、異?;蛉∠?。在所有這些情況下,方法都將返回 true。
 - isCancelled:如果此任務(wù)在正常完成之前被取消,則返回 true。
 - cancel:嘗試取消線程的執(zhí)行。請注意,這是嘗試取消,不一定能成功取消。因為任務(wù)可能已經(jīng)完成、被取消或由于其他一些因素而無法取消,所以取消可能會失敗。布爾類型的返回值表示取消是否成功。參數(shù) paramBoolean 表示是否通過中斷線程來取消線程執(zhí)行。
 
有時使用 Callable 而不是 Runnable 是為了能有取消任務(wù)的能力。如果使用 Future 只是為了可以取消任務(wù)但不提返回結(jié)果可以聲明 Future<? >的類型,并將底層任務(wù)的結(jié)果返回為 null。
你可能會問,為什么要有 FutureTask 類呢?前面說過 Future 只是一個接口,其方法 cancel、get、isDone 等如果自己實現(xiàn)會非常復(fù)雜。因此 JDK 為我們提供了 FutureTask 類供我們直接使用。
FutureTask 需要與 Callable 結(jié)合使用來完成有返回值的異步計算,這里看一個其使用的簡單示例:
class MyCallable implements Callable<Integer> {
    /**
     * 計算 1 到 4 的總和
     * @return
     */
    @Override
    public Integer call() {
        int res = 0;
        for (int i = 0; i < 5; i++) {
            res += i;
        }
        return res;
    }
}
publicclass CallableDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 1. 生成 MyCallable 的實例
        MyCallable myCallable = new MyCallable();
        // 2. 通過 myCallable 創(chuàng)建 FutureTask 對象
        FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
        // 3. 通過 FutureTask 創(chuàng)建 Thread 對象
        Thread t = new Thread(futureTask);
        // 4. 啟動線程
        t.start();
        // 5. 獲取計算結(jié)果
        Integer res = futureTask.get();
        System.out.println(res);
    }
}輸出:
10為什么只有一種實現(xiàn)線程的方法?
我相信你對這個問題基本上有了答案。無論你是實現(xiàn) Runnable 接口、實現(xiàn) Callable 接口還是直接繼承 Thread 類來創(chuàng)建線程,最終都是創(chuàng)建一個 Thread 實例來啟動線程,即 new Thread(),只是創(chuàng)建的形式不同而已!
實際上,線程不僅可以通過上述形式創(chuàng)建,還可以通過內(nèi)置的工具類(如線程池)來創(chuàng)建,后續(xù)文章將單獨介紹。
實現(xiàn) Runnable 接口優(yōu)于繼承 Thread 類
要實現(xiàn)一個沒有返回值的線程類,你可以繼承 Thread 類或?qū)崿F(xiàn) Runnable 接口,它們之間有什么優(yōu)缺點呢?
(1) Thread 類的優(yōu)點:
- 簡單直觀:由于繼承關(guān)系,代碼結(jié)構(gòu)相對簡單易懂。
 - 線程控制:可以直接使用 Thread 類的方法來控制線程的狀態(tài),如啟動、暫停、停止等。
 
(2) Thread 類的缺點:
- 單繼承限制:由于 Java 不支持多重繼承,使用 Thread 類限制了類的擴展。
 - 代碼耦合:線程類和線程執(zhí)行邏輯緊密耦合,不利于代碼復(fù)用和維護。
 
(3) Runnable 接口的優(yōu)點:
- 更好的代碼復(fù)用:由于它是一個接口,可以將線程的執(zhí)行邏輯與其他類分離,以實現(xiàn)代碼復(fù)用。
 - 靈活性:可以同時實現(xiàn)多個接口,避免單繼承的限制。
 - 更好的可擴展性:接口使得在不影響現(xiàn)有代碼的情況下擴展線程功能變得容易。即面向接口編程的原則。
 
(4) Runnable 接口的缺點:
- 代碼稍微復(fù)雜一些:需要創(chuàng)建一個實現(xiàn) Runnable 接口的類并實現(xiàn) run() 方法,然后由 Thread 類驅(qū)動。
 - 沒有線程控制方法:不能直接使用 Thread 類的線程控制方法,需要通過 Thread 對象調(diào)用它們。
 
所以,綜合考慮,通常建議使用 Runnable 接口的實現(xiàn)來創(chuàng)建線程,以獲得更好的代碼可復(fù)用性和可擴展性。
Thread 的 start() 方法
在程序中調(diào)用 start() 方法后,虛擬機首先為我們創(chuàng)建一個線程,然后等待直到這個線程獲得時間片,才會調(diào)用 run() 方法執(zhí)行具體邏輯。
請注意,start() 方法不能多次調(diào)用。第一次調(diào)用 start() 方法后,再次調(diào)用會拋出 IllegalThreadStateException 異常。
你可以簡單看一下 start() 方法的源代碼。實際上,實際工作是由 start0() 完成的,它是一個本地方法,我添加了一些注釋,以便你更容易理解。
public synchronized void start() {
    /**
     * 零狀態(tài)值對應(yīng)于狀態(tài) NEW。
     */
    if (threadStatus!= 0)
        thrownew IllegalThreadStateException();
    group.add(this); // 將其所屬的線程組加上該線程
    boolean started = false;
    try {
        start0(); // 本地方法調(diào)用實際創(chuàng)建線程的底層方法。
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadstartFailed(this);
            }
        } catch (Throwable ignore) {
            /* 什么也不做。如果 start0 拋出了 Throwable,那么
             * 它將在調(diào)用棧中向上傳遞 */
        }
    }
}ThreadGroup 的概念將在后續(xù)文章中介紹。你可以在這里忽略它。
Thread 類的幾個常用方法
這里我們簡要提及 Thread 類的幾個常用方法,先熟悉一下。后續(xù)文章將根據(jù)具體使用場景詳細介紹:
- currentThread():靜態(tài)方法,返回當前正在執(zhí)行的線程對象的引用;
 - sleep():靜態(tài)方法,使當前線程睡眠指定的時間;
 - yield():表示當前線程愿意放棄對當前處理器的占用。請注意,即使當前線程調(diào)用了 yield() 方法,它仍然有可能繼續(xù)運行;
 - join():使當前線程等待另一個線程完成執(zhí)行后再繼續(xù),內(nèi)部調(diào)用是通過 Object 類的 wait 方法實現(xiàn)的;
 
好了,這次就到這里,下次再見!















 
 
 









 
 
 
 