創建線程可以說是併發知識中最基礎的操作了,JDK 提供的創建線程的方式,如果不包括通過線程池的話,目前有三種形式,它們分別是通過繼承 Thread 類,通過實現 Runable 接口,通過 FutureTask。如下圖所示
下面整理了一下 3 種方法的具體使用與異同。
創建線程的 3 種方法
1. 繼承 Thread
- 創建一個類繼承 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());
}
}
}
- 創建並啓動線程
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());
}
整體流程如下:
這裏步驟比較簡單和清晰
- 創建一個類,這類要繼承 Thread
- 覆蓋 Thread 的 run 方法,並在此方法中實現你的多線程任務
- 創建這個類的實例
- 調用它的 start() 方法(這裏要注意,新手容易直接調用 run 方法,那樣只是普通調用,而不多線程調用)
2. 實現 Runable
- 創建一個類實現 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());
}
}
}
- 創建類的實現,並將它傳給 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());
}
整體流程如下:
具體步驟如下:
- 創建一個實現了 Runable 接口的類
- 覆蓋 run 方法,並在此方法中實現你的多線程任務
- 創建 Runable 接口實現類的實例
- 通過把上步得到的 Runable 接口實現類的實例,傳給 Thread 的有參構造函數來創建 Thread 的實例
- 調用上步得來的 Thread 實例的 start() 方法(這裏要注意,新手容易直接調用 run 方法,那樣只是普通調用,而不多線程調用)
3. 通過 FutureTask
- 創建一個實現了 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";
}
}
- 創建並啓動線程
// 創建異步任務
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();
}
整體流程如下:
具體步驟如下:
- 創建一個實現了 Callable
接口的類,這裏 T 的類型就是你線程任務想要返回的類型 - 覆蓋 call 方法,並在此方法中實現你的多線程任務
- 創建 Callable 接口實現類的實例
- 通過把上步得到的 Callable 接口實現類的實例,傳給 FutureTask 的有參構造函數來創建 FutureTask 的實例
- 通過把上步得到的 FutureTask 實例,傳給 Thread 的有參構造函數來創建 Thread 的實例
- 調用上步得來的 Thread 實例的 start() 方法(這裏要注意,新手容易直接調用 run 方法,那樣只是普通調用,而不多線程調用)
- 如果你還想獲得返回值,需要再調用 FutureTask 實例的 get() 方法(這裏要注意,get()會阻塞線程)
3種方法的優缺點
通過上述的演示代碼,可以看出這 3 種方法,其實各有優缺點
複雜程度
通過代碼量與邏輯可以明顯感覺出來,第一種直接繼承 Thread 最方便,並且其它兩種到最後,還是要依賴創建 Thread 才能實現。所以從方便及難易程度來看,可以得到如下結論:
可擴展性
通過演示代碼可以看出,只有第一種是通過繼承,其它兩種是通過實現接口的形式。我們都知道 JAVA 是不允許多繼承,但是可以多實現。所以如果使用了第一種方法,就無法再繼承別的類了。另外第一種把線程與線程任務冗餘在了一起,不利於後期的維護。所以可以得到如下結論:
是否有返回值
從代碼中可以很容易看出,只有通過 FutureTask 的方式纔有返回值,另外兩種均沒有,所以得出如下結論
該用哪種方式創建線程
如果要用到返回值,那不用想,肯定只能使用 FutureTask 的方法。如果對於返回值沒有要求,那Thread 與 Runable 均可,不過,考慮到可擴展性,最好使用 Runable 的形式。不過,話說回來,如果在真正項目中使用,綜合考慮,一般還是最推薦通過線程池去創建。