【線程內參】Java創建多線程的方式到底有幾種?

Java併發方面有很多書籍以及博客,針對於線程創建方式有着不同描述,例如實現Runnable接口、集成Thread類、使用線程池工具類以及結合Callable和Future創建線程等。

創建線程的兩種方式

Oracle官方文檔,即java.lang.Thread類註釋的表述是有如下兩種創建線程的方式。https://docs.oracle.com/javase/8/docs/api/index.html

方式一:實現Runnable接口,傳入Thread類

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類,重寫run方法

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類)解耦。
    與重寫Thread類run方法一樣,實現Runnable接口run方法的內容也是具體執行的任務。但是Runnable方式則可以創建單獨任務類實現Runnable接口,然後傳入任務實例到Thread類中。這樣同一個任務類可以傳給不同的Thread,同時任務類不需要負責創建線程等工作,因此是解耦的。

  • 繼承的角度上,Java是單繼承,如果繼承了Thread類就無法繼承其它類,限制可擴展性。

  • 資源節約的角度上講,如果是Thread類方式,每次新建一個任務只能新建一個獨立的線程,會有額外線程創建/銷燬等損耗。而Runnable更加方便採用線程池工具,減少創建、銷燬線程帶來的損耗。

兩種方式本質對比

兩種方式在多線程實現的本質上是一致的,都是調用thread對象的start方法創建線程。主要區別在於run方法的內容來源上。

# java.lang.Thread
private Runnable target;
@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

實現Runnable方式,最終調用了Runnable對象target的run方法;而繼承Thread方式是重寫了整個run方法。

示例:代碼同時使用兩種方式
匿名內部類實現Runnable接口,並在類內部重寫run方法。重寫run方法的代碼直接覆蓋Thread類run方法代碼,Runnable中run方法實現代碼不會執行。

Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("使用Runnable方式創建線程");
    }
}) {
    @Override
    public void run() {
        System.out.println("使用Thread方式創建線程");
    }
};
thread.start();

多線程的其他代碼實現形式

線程池創建線程的方式

public class ThreadPoolStyle {
    public static void main(String[] args) {
        // 不提倡的創建線程池方式--原因參見阿里規約
        ExecutorService pool = Executors.newCachedThreadPool();
        for (int i = 0; i < 1000; i++) {
            pool.submit(new Task());
        }
    }
}

class Task implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName());
    }
}

我們通過Executors創建線程池,深入源碼可以看到,內部創建ThreadPoolExecutor時會使用ThreadFactory。以DefaultThreadFactory爲例,其內部創建線程的方法newThread仍然是通過Runnable創建線程。

## java.util.concurrent.Executors
public Thread newThread(Runnable r) {
    Thread t = new Thread(group, r,
                          namePrefix + threadNumber.getAndIncrement(),
                          0);
    if (t.isDaemon())
        t.setDaemon(false);
    if (t.getPriority() != Thread.NORM_PRIORITY)
        t.setPriority(Thread.NORM_PRIORITY);
    return t;
}

通過Callable和FutureTask創建線程的方式

public class CallableFutureTaskStyle {
    public static void main(String[] args) {
        // 創建任務,啓動線程
        FutureTask<String> futureTask = new FutureTask<>(new CallableTask());
        new Thread(futureTask).start();
    }
}

class CallableTask implements Callable {
    @Override
    public String call() throws Exception {
        return "使用Callable和FutureTask創建線程";
    }
}

Callable接口是一個獨立的接口,用於創建任務,通常與FutureTask配合使用。而FutureTask的父類是Runnable,因此其本質仍然是Runnable方式創建線程。

在這裏插入圖片描述

定時器創建線程

public class TimerStyle {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        },100,100);
    }
}

定時器任務類TimerTask實現Runnable接口。

public abstract class TimerTask implements Runnable

匿名內部類和Lambda代碼實現形式

多線程的代碼實現方式有很多種,但其本質仍然是出於繼承Thread和實現Runnable接口兩種方式。線程池、Callable以及定時器,對創建線程做了一定的封裝,但本質仍然沒有變。至於線程匿名內部類和Lambda實現,只是代碼實現形式的不同,不能作爲實現線程的方式。

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}).start();
new Thread(() -> System.out.println(Thread.currentThread().getName())).start();

較爲準確的描述

(1)首先從不同的角度看,會有不同的答案。
從本質上講,創建線程只有一種方式,那就是創建Thread類,通過start方法啓動。實現線程的執行單元有兩種方式,分別是Runnable方式和Thread方式。Oracle文檔也是這樣註釋的。
(2)實現Runnable接口,作爲Thread構造參數;重寫Thread類run方法。兩種方式在創建線程的本質上是一樣的,都是創建Thread對象,主要區別在於run方法內容的來源不同:Runnable方式最終是調用Ruannble對象target的run方法,而Thread方式則是使用了重寫的run方法。
(3)對於Runnable方式和Thread方式,優先使用Runnable方式。(3個角度)
(4)除此之外,從表面上看線程池、定時器等工具類也可創建線程,但是本質仍然是Runnable方式。

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