天天看點

Java 并發系列之一

Java 并發系列之一

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

線程與程序

程序是作業系統配置設定資源的基本機關,也就是說程序是運作中的程式。

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

處理并發問題為什麼使用多線程而不是多程序,在我看來主要有兩點。一是程序間通信難度大于線程間通信,會增加開發難度,二是線程間切換效率高于程序間切換,選擇多線程更适合并發場景。

線程的生命周期

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 接口