Java 有三種方式實現多線程,繼承 Thread 類、實現 Runnable 接口、實現 Callable 接口。還有匿名內部類方式,Lambda 表達式方式簡化開發。
1、Thread
Thread 創建線程方式:創建線程類
-
start() 方法底層其實是給 CPU 注冊當前線程,并且觸發 run() 方法執行
-
線程的啟動必須調用 start() 方法,如果線程直接調用 run() 方法,相當于變成了普通類的執行,此時主線程將只有執行該線程
-
建議線程先創建子線程,主線程的任務放在之后,否則主線程(main)永遠是先執行完
Thread 構造器:
-
public Thread()
-
public Thread(String name)
public class p1 {public static void main(String[] args) {// 創建線程類方式Thread t1 = new MyThread();t1.start();}
}
class MyThread extends Thread{@Overridepublic void run() {for(int i = 0 ; i < 100 ; i++ ) {// 子線程輸出System.out.println(Thread.currentThread().getName() + "->" + i);}}
}
繼承 Thread 類的優缺點:
-
優點:編碼簡單
-
缺點:線程類已經繼承了 Thread 類無法繼承其他類了,功能不能通過繼承拓展(單繼承的局限性)
2、Runnable
Runnable 創建線程方式:創建線程類
Thread 的構造器:
-
public Thread(Runnable target)
-
public Thread(Runnable target, String name)
public class p2 {public static void main(String[] args) {Runnable target = new MyRunnable();Thread t1 = new Thread(target,"Runnable線程");t1.start();Thread t2 = new Thread(target);//Thread-0}
}public class MyRunnable implements Runnable{@Overridepublic void run() {for(int i = 0 ; i < 10 ; i++ ){System.out.println(Thread.currentThread().getName() + "->" + i);}}
}
Thread 類本身也是實現了 Runnable 接口,Thread 類中持有 Runnable 的屬性,執行線程 run 方法底層是調用 Runnable#run:
?public class Thread implements Runnable {private Runnable target;public void run() {if (target != null) {// 底層調用的是 Runnable 的 run 方法target.run();}}}
Runnable 方式的優缺點:
-
缺點:代碼復雜一點。
-
優點:
-
線程任務類只是實現了 Runnable 接口,可以繼續繼承其他類,避免了單繼承的局限性
-
同一個線程任務對象可以被包裝成多個線程對象
-
適合多個多個線程去共享同一個資源
-
實現解耦操作,線程任務代碼可以被多個線程共享,線程任務代碼和線程獨立
-
線程池可以放入實現 Runnable 或 Callable 線程任務對象
-
3、Callable
實現 Callable 接口:
定義一個線程任務類實現 Callable 接口,申明線程執行的結果類型
重寫線程任務類的 call 方法,這個方法可以直接返回執行的結果
創建一個 Callable 的線程任務對象
把 Callable 的線程任務對象包裝成一個未來任務對象
把未來任務對象包裝成線程對象
調用線程的 start() 方法啟動線程
public FutureTask(Callable<V> callable)
:未來任務對象,在線程執行完后得到線程的執行結果
-
FutureTask 就是 Runnable 對象,因為 Thread 類只能執行 Runnable 實例的任務對象,所以把 Callable 包裝成未來任務對象
public V get()
:同步等待 task 執行完畢的結果,如果在線程中獲取另一個線程執行結果,會阻塞等待,用于線程同步
-
get() 線程會阻塞等待任務執行完成
-
run() 執行完后會把結果設置到 FutureTask 的一個成員變量,get() 線程可以獲取到該變量的值
public class p3 {public static void main(String[] args) {Callable<String> call = new MyCallable();FutureTask<String> task = new FutureTask<>(call);Thread t1 = new Thread(task);t1.start();try {// 獲取call方法返回的結果(正常/異常結果)String s = task.get(); System.out.println(s);} catch (Exception e) {e.printStackTrace();}}public class MyCallable implements Callable<String> {@Overridepublic String call() throws Exception {return Thread.currentThread().getName() + "->" + "MyCallable String";}
}
優缺點:
-
優點:同 Runnable,并且能得到線程執行的結果
-
缺點:編碼復雜
4、匿名內部類方式、Lambda 表達式方式
public class p1 {public static void main(String[] args) {// 匿名內部類方式Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("t1 線程執行中");}});t1.start();// Lambda 表達式創建匿名內部類的線程對象Thread t2= new Thread(() -> {System.out.println("t2 線程執行中");});t2.start();}
}
使用匿名內部類的方式可以方便地定義并實例化線程對象,并實現線程的執行邏輯。它對于一些簡單的線程任務可以簡潔地表達,但對于復雜的線程邏輯,建議使用具名的內部類或者單獨定義一個類來實現Runnable接口。
使用 lambda 表達式代替了匿名內部類,使得代碼更加簡潔。適用于只包含一個抽象方法的接口,例如 Runnable 接口和 Callable 接口。對于其他接口,如果包含多個抽象方法,就無法使用 lambda 表達式來創建匿名內部類。
借鑒:https://github.com/Seazean/JavaNote/blob/main/Prog.md