Java 並發系列之一

  • 2020 年 3 月 14 日
  • 筆記

Java 並發系列之一

簡單的總結了一些 Java 常用的集合之後,發現許多集合都針對多執行緒提供了支援,比如 ConcurrentHashMap 使用分段鎖來提高多執行緒環境下的性能表現與安全表現。所以我打算接著對 Java 並發的相關內容做一個簡單總結。

執行緒與進程

進程是作業系統分配資源的基本單位,也就是說進程是運行中的程式。

執行緒是進程中的基本執行單元,每個進程都至少擁有一個執行緒。執行緒不獨立擁有作業系統資源,執行緒共享進程的作業系統資源。

處理並發問題為什麼使用多執行緒而不是多進程,在我看來主要有兩點。一是進程間通訊難度大於執行緒間通訊,會增加開發難度,二是執行緒間切換效率高於進程間切換,選擇多執行緒更適合併發場景。

執行緒的生命周期

這裡我們只簡單介紹執行緒 new->Runnable->Running->Dead 這個流程,不考慮 Block 的情況。

  1. 當我們創建一個 Thread 對象時,這個執行緒就進入了 new 的狀態。
  2. 當時機成熟,我們調用這個對象的 start() 方法時,這個執行緒就進入了 Runnable 的狀態。
  3. 然後這個執行緒就會等待 CPU 資源,如果獲取到 CPU 資源就會自動運行。
  4. 運行結束後執行緒就會進入到 Dead 狀態。

需要注意的是一個 Thread 對象只有一次調用 start() 方法的機會,無論這個執行緒是否順利執行結束。

創建執行緒

創建執行緒也就是執行緒生命周期中的 new 狀態。在 Java 中創建執行緒有 3 中方式:

  1. 繼承 Thread 類並重寫 run 方法
  2. 實現 Runnable 介面
  3. 實現 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 創建執行緒分為以下幾個步驟:

  1. 創建類實現 Callable 介面的 call() 方法
  2. 使用 FutureTask 來包裝上一步創建的類
  3. 使用 FutureTask 來創建 Thread 類
  4. 使用 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

小結

  1. 如果沒有特殊的需求,實現 Runnable 介面或許是一個比較好的選擇
  2. 如果需要執行緒執行完成後提供返回值,就只能選擇繼承 Callable 介面