【併發那些事 】建立線程的三種方式

建立線程能夠說是併發知識中最基礎的操做了,JDK 提供的建立線程的方式,若是不包括經過線程池的話,目前有三種形式,它們分別是經過繼承 Thread 類,經過實現 Runable 接口,經過 FutureTask。以下圖所示多線程

下面整理了一下 3 種方法的具體使用與異同。併發

建立線程的 3 種方法

1. 繼承 Thread

  1. 建立一個類繼承 Thread 並覆蓋 run 方法
class MyThread extends Thread {
    @Override
    public void run() {
        String threadName = getName();
        for (int i = 0; i < 20; i++) {
            System.out.println("線程[" + threadName + "]運行開始,i = " + i + " time = " + new Date());
        }
    }
}複製代碼

  1. 建立並啓動線程
MyThread myThread1 = new MyThread();
        MyThread myThread2 = new MyThread();

        myThread1.start();
        myThread2.start();

        String threadName = Thread.currentThread().getName();
        for (int i = 0; i < 20; i++) {
            System.out.println("線程[" + threadName + "]運行開始,i = " + i + " time = " + new Date());
        }複製代碼

總體流程以下:異步

這裏步驟比較簡單和清晰ide

  1. 建立一個類,這類要繼承 Thread
  2. 覆蓋 Thread 的 run 方法,並在此方法中實現你的多線程任務
  3. 建立這個類的實例
  4. 調用它的 start() 方法(這裏要注意,新手容易直接調用 run 方法,那樣只是普通調用,而很少線程調用)

2. 實現 Runable

  1. 建立一個類實現 Runable 接口,並覆蓋 run 方法
class MyRunable implements Runnable {
    
    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        for (int i = 0; i < 20; i++) {
            System.out.println("線程[" + threadName + "]運行開始,i = " + i + " time = " + new Date());
        }
    }
}複製代碼

  1. 建立類的實現,並將它傳給 Thread 的構造函數來建立 Thread 
MyRunable myRunable = new MyRunable();

new Thread(myRunable).start();
new Thread(myRunable).start();

String threadName = Thread.currentThread().getName();
for (int i = 0; i < 20; i++) {
    System.out.println("線程[" + threadName + "]運行開始,i = " + i + " time = " + new Date());
}複製代碼

總體流程以下:svg

具體步驟以下:函數

  1. 建立一個實現了 Runable 接口的類
  2. 覆蓋 run 方法,並在此方法中實現你的多線程任務
  3. 建立 Runable 接口實現類的實例
  4. 經過把上步獲得的 Runable 接口實現類的實例,傳給  Thread 的有參構造函數來建立 Thread 的實例
  5. 調用上步得來的 Thread 實例的 start() 方法(這裏要注意,新手容易直接調用 run 方法,那樣只是普通調用,而很少線程調用)

3. 經過 FutureTask

  1. 建立一個實現了 Callable 接口的類,並實現call 方法 (T 表明你想要的返回值類型)
class MyCallerTask implements Callable<String> {
    
    @Override
    public String call() throws Exception {
        System.out.println("執行任務開始");
        Thread.sleep(3000);
        System.out.println("執行任務結束");
        return "hello";
    }
}複製代碼

  1. 建立並啓動線程
// 建立異步任務
        FutureTask<String> futureTask = new FutureTask<>(new MyCallerTask());
        // 啓動線程
        new Thread(futureTask).start();
        System.out.println("其它操做");
        try {
            // 等待任務執行完,並得到任務執行完的結果
            String result = futureTask.get();
            System.out.println(result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }複製代碼

總體流程以下:spa

具體步驟以下:線程

  1. 建立一個實現了 Callable 接口的類,這裏 T 的類型就是你線程任務想要返回的類型
  2. 覆蓋 call 方法,並在此方法中實現你的多線程任務
  3. 建立 Callable 接口實現類的實例
  4. 經過把上步獲得的 Callable 接口實現類的實例,傳給  FutureTask 的有參構造函數來建立 FutureTask 的實例
  5. 經過把上步獲得的 FutureTask 實例,傳給  Thread 的有參構造函數來建立 Thread 的實例
  6. 調用上步得來的 Thread 實例的 start() 方法(這裏要注意,新手容易直接調用 run 方法,那樣只是普通調用,而很少線程調用)
  7. 若是你還想得到返回值,須要再調用 FutureTask 實例的 get() 方法(這裏要注意,get()會阻塞線程)

3種方法的優缺點

經過上述的演示代碼,能夠看出這 3 種方法,其實各有優缺點code

複雜程度

經過代碼量與邏輯能夠明顯感受出來,第一種直接繼承 Thread 最方便,而且其它兩種到最後,仍是要依賴建立 Thread 才能實現。因此從方便及難易程度來看,能夠獲得以下結論:![](http://static.coder4j.cn/yuque/latex/8815a238f7257e13ab9459d03677c3f2.svg#card=math&code=Thread%20%20%3E%20Runable%20%3E%20FutureTask&height=16&width=251)cdn

可擴展性

經過演示代碼能夠看出,只有第一種是經過繼承,其它兩種是經過實現接口的形式。咱們都知道 JAVA 是不容許多繼承,可是能夠多實現。因此若是使用了第一種方法,就沒法再繼承別的類了。另外第一種把線程與線程任務冗餘在了一塊兒,不利於後期的維護。因此能夠獲得以下結論:

是否有返回值

從代碼中能夠很容易看出,只有經過 FutureTask 的方式纔有返回值,另外兩種均沒有,因此得出以下結論

該用哪一種方式建立線程

若是要用到返回值,那不用想,確定只能使用 FutureTask 的方法。若是對於返回值沒有要求,那Thread 與  Runable 都可,不過,考慮到可擴展性,最好使用 Runable 的形式。不過,話說回來,若是在真正項目中使用,綜合考慮,通常仍是最推薦經過線程池去建立。

相關文章
相關標籤/搜索