本文總結自《瘋狂Java講義》
一、繼承Thread類
- 定義 Thread 類的子類,並重寫該類的 run() 方法,該 run() 方法的方法體就代表了線程需要完成的任務。因此把 run() 方法稱爲線程執行體。
- 創建 Thread 子類的實例,即創建了線程對象。
- 調用線程對象的 start() 方法來啓動該線程。
示例代碼如下:
public class ThreadTest extends Thread{
private int x;
public void run(){
for (; x < 10; x++) {
System.out.println(getName() + " " + x); //獲取線程名字
}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+ " " + i); //獲取當前線程的名字
if(i == 5){
new ThreadTest().start();
new ThreadTest().start();
}
}
}
}
輸出結果:
main 0
main 1
main 2
main 3
main 4
main 5
main 6
main 7
main 8
main 9
Thread-0 0
Thread-0 1
Thread-0 2
Thread-0 3
Thread-1 0
Thread-0 4
Thread-1 1
Thread-1 2
Thread-1 3
Thread-1 4
Thread-1 5
Thread-1 6
Thread-1 7
Thread-1 8
Thread-1 9
Thread-0 5
Thread-0 6
Thread-0 7
Thread-0 8
Thread-0 9
我們可以看到,main函數是最先執行完的,因爲開闢一個線程後,該線程就會進入就緒狀態,並不會直接進入運行狀態,狀態的切換由底層平臺控制的,有一定的隨機性。
如果main函數的循環次數再大一些,線程0和線程1就能在main函數執行完前開始執行。
二、實現Runnable接口
- 定義 Runnable 接口的實現類,並重寫該接口的 run() 方法,該 run() 方法的方法體同樣是該線程的線程執行體。
- 創建 Runnable 實現類的實例,並以此實例作爲 Thread 的 target 來創建 Thread 對象,該 Thread 對象纔是真正的線程對象。
- 調用線程對象的 start() 方法來啓動線程
示例代碼如下:
public class ThreadTest implements Runnable{
private int x;
public void run(){
for (; x < 10; x++) {
System.out.println(Thread.currentThread().getName() + " " + x);
}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+ " " + i);
if(i == 0){
new Thread(new ThreadTest()).start();
new Thread(new ThreadTest()).start();
}
}
}
}
輸出結果:
main 0
main 1
main 2
main 3
main 4
main 5
main 6
main 7
main 8
main 9
Thread-0 0
Thread-1 0
Thread-1 1
Thread-1 2
Thread-0 1
Thread-1 3
Thread-0 2
Thread-0 3
Thread-0 4
Thread-0 5
Thread-0 6
Thread-1 4
Thread-1 5
Thread-1 6
Thread-1 7
Thread-1 8
Thread-1 9
Thread-0 7
Thread-0 8
Thread-0 9
區別主要在於,線程的創建是將實現了 Runnable 的類的實例作爲參數,通過 new Thread(new threadTest())
方法來創建,再調用線程的 start() 方法執行線程。
三、使用Callable和Future
- 創建 Callable 接口的實現類 ,並實現 call() 方法,該 call() 方法將作爲線程執行體,且該 call() 方法有返回值,再創建 Callable 實現類的實例。
- 使用 FutureTask 類來包裝 Callable 對象,該 FutureTask 對象封裝了該Callable 對象的 call() 方法的返回值。
- 使用 FutureTask 對象作爲 Thread 對象的 target 創建並啓動新線程。
- 調用 FutureTask 對象的 get() 方法來獲得子線程執行結束後的返回值。
示例代碼如下:
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThreadTest {
public static void main(String[] args) {
FutureTask<Integer> task = new FutureTask<Integer>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int i = 0;
for (; i < 10; i++){
System.out.println(Thread.currentThread().getName() + " 的循環變量i的值:" + i);
}
return i;
}
});
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "的循環變量i的值:" + i);
if (i == 5){
new Thread(task, "有返回值的線程").start();
}
}
try{
System.out.println("子線程的返回值:" + task.get());
}catch (Exception e){
e.printStackTrace();
}
}
}
輸出結果:
main的循環變量i的值:0
main的循環變量i的值:1
main的循環變量i的值:2
main的循環變量i的值:3
main的循環變量i的值:4
main的循環變量i的值:5
main的循環變量i的值:6
main的循環變量i的值:7
main的循環變量i的值:8
main的循環變量i的值:9
有返回值的線程 的循環變量i的值:0
有返回值的線程 的循環變量i的值:1
有返回值的線程 的循環變量i的值:2
有返回值的線程 的循環變量i的值:3
有返回值的線程 的循環變量i的值:4
有返回值的線程 的循環變量i的值:5
有返回值的線程 的循環變量i的值:6
有返回值的線程 的循環變量i的值:7
有返回值的線程 的循環變量i的值:8
有返回值的線程 的循環變量i的值:9
子線程的返回值:10
Callable 接口看起來就像是 Runnable 接口的增強版,call() 作爲線程執行體,可以有返回值,還可以拋出異常。
Callable 接口沒有實現 Runnable 接口,不能作爲 Thread 的 target ,無法直接創建線程對象。
Callable 接口有泛型限制,返回值類型要與泛型類型一致。
Future 接口代表 Callable 接口裏 call() 方法的返回值,並提供了一個實現類 FutureTask ,該實現類也實現了 Runnable 接口,因此可以將 FutureTask 與 Callable 關聯起來,實現多線程。
三種方法的對比
- 繼承 Thread 的方法已經繼承了 Thread 類,無法再繼承別的類,而 Runnable 和 Callable 可以繼承別的類。
- Runnable 和 Callable 可以多個線程共享同一個 target 對象,所以非常適合多個相同線程來處理同一份資源的情況,從而可以將CPU、代碼、數據分開,形成清晰的模型,較好地體現了面向對象的思想。
- Callable 類的線程可以經過 Future 包裝後獲取返回值。