Java 並發系列之一
- 2020 年 3 月 14 日
- 筆記
Java 並發系列之一
簡單的總結了一些 Java 常用的集合之後,發現許多集合都針對多執行緒提供了支援,比如 ConcurrentHashMap 使用分段鎖來提高多執行緒環境下的性能表現與安全表現。所以我打算接著對 Java 並發的相關內容做一個簡單總結。
執行緒與進程
進程是作業系統分配資源的基本單位,也就是說進程是運行中的程式。
執行緒是進程中的基本執行單元,每個進程都至少擁有一個執行緒。執行緒不獨立擁有作業系統資源,執行緒共享進程的作業系統資源。
處理並發問題為什麼使用多執行緒而不是多進程,在我看來主要有兩點。一是進程間通訊難度大於執行緒間通訊,會增加開發難度,二是執行緒間切換效率高於進程間切換,選擇多執行緒更適合併發場景。
執行緒的生命周期
這裡我們只簡單介紹執行緒 new->Runnable->Running->Dead 這個流程,不考慮 Block 的情況。
- 當我們創建一個 Thread 對象時,這個執行緒就進入了 new 的狀態。
- 當時機成熟,我們調用這個對象的 start() 方法時,這個執行緒就進入了 Runnable 的狀態。
- 然後這個執行緒就會等待 CPU 資源,如果獲取到 CPU 資源就會自動運行。
- 運行結束後執行緒就會進入到 Dead 狀態。
需要注意的是一個 Thread 對象只有一次調用 start() 方法的機會,無論這個執行緒是否順利執行結束。
創建執行緒
創建執行緒也就是執行緒生命周期中的 new 狀態。在 Java 中創建執行緒有 3 中方式:
- 繼承 Thread 類並重寫 run 方法
- 實現 Runnable 介面
- 實現 Callable 介面
繼承 Thread 類來創建執行緒
使用繼承 Thread 的方式可以直接在 run 方法中利用 this 來獲取當前執行緒的資訊,而不需要通過 Thread 類的靜態方法 currentThread() 方法來獲取當前的執行緒。
public class MyThread extends Thread { @Override public void run() { System.out.println(this.getName()+"正在運行"); } } public static void main(String[] args) { Thread thread = new MyThread(); thread.start(); } //輸出結果:Thread-0 正在運行
如果想要快速實現一個匿名的類來執行某個簡單操作的話,可以用下面的方式:
public static void main(String[] args) { new Thread() { @Override public void run() { System.out.println(this.getName() + "正在執行"); } }.start(); } //輸出結果:Thread-0 正在運行
實現 Runnable 介面來創建執行緒
由於 Java 是不支援多繼承的,所以如果要繼承 Thread 類以外的類,使用 Runnable 來實現或許是個不錯的選擇。
public class MyRunnable implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName()+"正在運行"); } } public static void main(String[] args) { Runnable runnable = new MyRunnable(); Thread thread = new Thread(runnable); thread.start(); } //輸出結果:Thread-0 正在運行
其實 Runnable 介面只包含一個 run 方法,本質上還是通過重寫 Thread 類的 run 方法來達到創建執行緒的目的。Runnable 還是一個函數式介面,所以想要聲明一個匿名的 Thread 類還能通過下面的方式:
public static void main(String[] args) { new Thread(() -> { System.out.println(Thread.currentThread().getName() + "正在運行"); }).start(); } //輸出結果:Thread-0 正在運行
實現 Callable 介面來創建執行緒
上面兩種創建執行緒的方式都是不支援返回值的,如果需要執行緒在執行之後提供返回值,就可以通過 Callable 來創建執行緒。使用 Callable 創建執行緒分為以下幾個步驟:
- 創建類實現 Callable 介面的 call() 方法
- 使用 FutureTask 來包裝上一步創建的類
- 使用 FutureTask 來創建 Thread 類
- 使用 FutureTask 對象的 get() 方法來獲取返回值
public class MyCallable implements Callable<Integer> { @Override public Integer call() throws Exception { System.out.println(Thread.currentThread().getName()+"正在運行"); return 316495132; } } public static void main(String[] args) throws ExecutionException,InterruptedException { Callable callable = new MyCallable(); FutureTask<Integer> futureTask = new FutureTask<>(callable); Thread thread = new Thread(futureTask); thread.start(); System.out.println("返回值為:" + futureTask.get()); } //輸出結果: //Thread-0 正在運行 //返回值為:316495132
小結
- 如果沒有特殊的需求,實現 Runnable 介面或許是一個比較好的選擇
- 如果需要執行緒執行完成後提供返回值,就只能選擇繼承 Callable 介面