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