【併發那些事 】建立執行緒的三種方式
阿新 • • 發佈:2019-10-17
建立執行緒可以說是併發知識中最基礎的操作了,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 的形式。不過,話說回來,如果在真正專案中使用,綜合考慮,一般還是最推薦通過執行緒池去建立