天天看點

Java多線程:線程的建立與啟動

面試題:

  • Java中建立線程有幾種方式。
  • 不同的建立方式有什麼差別。
  • 如何啟動一個線程。

Java中建立線程的方式

  • JDK源碼中的描述:兩種
  • 第一種是繼承Thread類,重寫其run()方法()。
  • 第二種是實作Runnable接口,重寫run()方法,再将Runnable執行個體傳給Thread,Thread類最終會調用

    target.run()

    (target即為Runnable執行個體)方法來執行。
  • 代碼示範
/**
 * 通過繼承Thread類,重寫run()方法實作的線程
 *
 * @author futao
 * @date 2020/6/4
 */
public class ByThread extends Thread {

    @Override
    public void run() {
        System.out.println("通過繼承Thread實作的多線程:" + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        new ByThread().start();
    }
}


/**
 * 通過實作Runnable接口,重寫run()方法實作的線程
 *
 * @author futao
 * @date 2020/6/4
 */
public class ByRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("通過實作接口Runnable實作的多線程:" + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        new Thread(new ByRunnable()).start();
    }
}           

核心思想

從源代碼的角度來看,建立線程的方式隻有一種,唯一的途徑就是執行個體化一個Thread對象,通過

thread.start()

來啟動線程。而繼承Thread類和實作Runnable接口,隻不過是對方法執行單元(即

run()

方法)的兩種不同實作。

兩種方式的對比:

  1. 從代碼架構角度:使用Runnable接口得我方式可以将線程的建立/停止/狀态管理等與真正的業務邏輯解耦,使Runnable子類隻需要關注真正的業務即可。
  2. 從性能損耗角度:使用繼承Thread的方法,每次執行任務都需要啟動一個新的線程,建立一個新的Thread執行個體,任務執行完畢之後還需要進行銷毀。對性能的損耗比較嚴重。而實作Runnable接口的方式,可以實作對線程的複用,每次給線程傳遞不同的任務(Runnable執行個體)即可,不需要頻繁的建立與銷毀線程。( 參考線程池的執行過程
  3. 從可擴充性角度:Java隻支援單繼承,是以實作Runnable接口的方式更好,避免繼承的局限,友善後續對程式進行擴充。

Q: 如果同時繼承Thread類和實作Runnable接口,會發生什麼?

/**
 * @author futao
 * @date 2020/6/4
 */
public class Both {

    public static void main(String[] args) {
        new Thread(() -> {
            //通過lambda表達式建立Runnable子類對象
            System.out.println("來自實作Runnable接口的run()方法");
        }) {
            //Thread的匿名内部類,直接重寫Thread父類的run()方法
            @Override
            public void run() {
                System.out.println("來自重寫Thread類的run()方法");
            }
        }.start();
    }
}           
  • 從結果可以看到,最終線程執行的是匿名内部類的run()方法。原因是在我們将Thread的start()方法重寫之後不會再執行調用Runnable.run()方法。而執行我們重寫之後的run()方法。
@Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }           

錯誤觀點

  • 線程池: 通過線程工廠

    ThreadFactory.newThread()

    建立線程,而

    ThreadFactory.newThread()

    方法中也是通過執行個體化Thread對象的方式建立線程。
ExecutorService executorService = Executors.newCachedThreadPool(new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r);
            }
        });           
  • 通過

    FutureTask<>

    Callable

    : FutureTask實際上實作了Runnable接口,并且在重寫的run()方法中調用了傳遞進來的Callable對象的call()方法。是以這種方式隻不過是對Runnable方式的一種封裝而已。本質上也隻是實作線程執行單元的一種方法,最終需要将FutureTask對象傳入Thread()對象進行執行。
/**
 * @author futao
 * @date 2020/6/6.
 */
public class CallableFutureTask {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> task = new FutureTask<>(new Callable<String>() {
            @Override
            public String call() throws Exception {
                System.out.println("666");
                Thread.sleep(3000L);
                return Thread.currentThread().getName();
            }
        });
        new Thread(task).start();
        //将會阻塞,直到線程任務執行完畢
        System.out.println(task.get());
    }
}           

線程的啟動

  • 調用Thread對象的

    start()

    方法。三個過程:
    1. 判斷線程狀态。
    2. 加入線程組。
    3. 執行native方法

      start0()

  • 直接調用

    run()

    方法:隻是普通方法調用,不會開啟新的線程。
  • start()

    方法隻能被調用一次,如果第二次調用,将抛出異常,即啟動過程的第一步:檢查線程狀态不通過。

本文源代碼