《java 核心技術》這本書真的不錯,知識點很全面,翻譯質量也還不錯,本系列博文是對該書中併發章節的一個總結。
什麼是執行緒
官方解釋:執行緒是作業系統能夠進行運算排程的最小單位,包含於程式之中,是程式中的實際運作單位。也就是說執行緒是程式碼執行的載體,我們所編寫的程式碼都是線上程上跑的,以一個最簡單的 hellowWorld 為例:
public class Main {
public static void main(String[] args) {
System.out.println("Hello World!");
System.out.println("當前執行緒名為:"+Thread.currentThread().getName());
System.out.println("當前執行緒id為:"+Thread.currentThread().getId());
}
}
結果為:
Hello World!
當前執行緒名為:main
當前執行緒id為:1
在程式執行時預設會建立一個主執行緒來執行程式碼,執行緒名為:main,執行緒 id 為 1
什麼是多執行緒
顧名思義就是多個執行緒同時執行,提高程式執行速度。單個執行緒一次只能做一件事,想要提高執行效率有兩種途徑:
- 非同步。因為大多數時候執行緒都不是時刻在進行計算,都是在等待 io 操作,那麼就可以將等待時間利用起來以提高執行緒的利用率。這裡不做過多討論,想要進一步瞭解非同步的可以學習 Node.js(原生支援非同步)
- 多執行緒。一個執行緒一次只能做一件事,那麼多個執行緒就能同時做多件事了,通過增大執行緒數來提高執行速度。
如何建立執行緒
建立執行緒有兩種方法
- 繼承 Thread 類
- 實現 runnable 介面
繼承 Thread 類
不推薦本方式來建立執行緒,原因顯而易見:java 不支援多繼承,如果繼承了 Thread 類就不能再繼承其他類了。
使用繼承方式建立執行緒程式碼如下:
public class CustomThreadExtendThread extends Thread{
@Override
public void run() {
String threadName = Thread.currentThread().getName();
long threadId = Thread.currentThread().getId();
System.out.println("建立執行緒名為:"+threadName+",id為:"+threadId);
}
public static void main(String[] args){
Thread thread1 = new CustomThreadExtendThread();
Thread thread2 = new CustomThreadExtendThread();
thread1.start();
thread2.start();
}
}
實現 runnable 介面
實現介面來建立執行緒是目前推薦的一種方式,原因也很簡單:一個類可以實現多個介面。實現 Runnable 介面並不影響實現類再去實現其他介面。
使用實現介面方式建立執行緒程式碼如下:
public class CustomThreadImplementInterface implements Runnable {
@Override
public void run() {
Thread.currentThread().setName(((Double) Math.random()).toString());
String threadName = Thread.currentThread().getName();
long threadId = Thread.currentThread().getId();
System.out.println("建立執行緒名為:" + threadName + ",id為:" + threadId);
}
public static void main(String[] args) {
Thread thread1 = new Thread(new CustomThreadImplementInterface());
Thread thread2 = new Thread(new CustomThreadExtendThread());
thread1.start();
thread2.start();
//使用lambda表示式,讓建立執行緒更簡單
new Thread(() -> {
System.out.println("建立了一個新執行緒");
}).start();
}
}
通過檢視 Thread 原始碼可以看到 Thread 類也是 Runnable 介面的一個實現類。
PS:後續程式碼全部使用 runnable 建立執行緒
執行緒狀態
上面只是演示了執行緒的建立,現在來詳細瞭解執行緒的狀態。在 java 規範中,執行緒可以有以下 6 種狀態:
- New(新建立)
- Runnable(可執行)
- Blocked(阻塞)
- Waiting(等待)
- Timed waiting(計時等待)
- Terminated(被終止)
新建立執行緒
當使用 new 操作符建立一個執行緒時,如 new Thread(r),執行緒還未開始執行,就屬於新建立狀態。
可執行執行緒
一旦呼叫 Thread 類的 start 方法,執行緒就處於可執行狀態。
為什麼要叫可執行狀態?
因為 Java 的規範中並沒有將正在 CPU 上執行定義為一個單獨的狀態。因此處於可執行狀態的執行緒可能正在執行,也可能沒有執行,取決於 CPU 的排程策略。
被阻塞執行緒和等待執行緒
當執行緒處於阻塞或等待狀態時,不執行任何程式碼且消耗最少的資源。直到重新執行。有如下幾種途徑讓執行緒進入阻塞或等待狀態:
- 當一個執行緒試圖獲取一個內部的物件鎖,而該鎖被其他執行緒持有
- 當執行緒等待另一個執行緒通知排程器一個條件時,進入等待狀態。比如呼叫 Object.wait 或 Thread.join 方法,或等待 java.util.concurrent 庫中的 Lock 或 Condition 時。
- 當呼叫計時等待方法時。比如 Thread.sleep,Object.wait,Thread.join,Lock.tryLock 以及 Condition.await
被終止的執行緒
執行緒可由以下兩種辦法進入終止狀態:
- run 方法的結束而自然死亡
- 未捕獲異常中止了 run 方法而意外死亡
注意: 呼叫執行緒的 stop 方法也可以終止執行緒,但是這個方法已經被棄用,最好不要使用。
執行緒屬性
執行緒有各種屬性:優先順序,守護執行緒,執行緒組以及處理未捕獲異常處理器。
執行緒優先順序
java 中,每個執行緒都有一個優先順序。預設情況下,執行緒繼承父執行緒優先順序。也可以呼叫setPriority
方法指定優先順序。優先順序範圍:1(MIN_PRIORITY)-10(MAX_PRIORITY).NORM_PRIORITY 為 5,這些常量定義在 Thread 類中.
注意: 執行緒優先順序時高度依賴於系統的,因此當 java 執行緒優先順序對映到宿主機平臺的優先順序時,優先順序個數可能會變少或者變成 0.比如,Windows 中有 7 個優先順序,java 執行緒對映時部分優先順序將會對映到相同的作業系統優先順序上。Oracle 為 Linux 編寫的 java 虛擬機器中,忽略了執行緒的優先順序,所有 java 執行緒都有相同的優先順序。不要編寫依賴優先順序的程式碼。
守護執行緒
通過呼叫Thread.setDaemon(true)
將一個執行緒轉換為守護執行緒。守護執行緒唯一的使用者是為其他執行緒提供服務,比如計時執行緒,定時傳送計時訊號給其他執行緒。因此當虛擬機器中只有守護執行緒時,虛擬機器就會關閉退出。不要在守護執行緒中訪問任何資源,處理任何業務邏輯
未捕獲異常處理器
執行緒的 run 方法不能丟擲任何受查異常,非受查異常會導致執行緒終止,除了 try/catch 捕獲異常外,還可以通過未捕獲異常處理器來處理異常。異常處理器需要實現Thread.UncaughtExceptionHandler
介面。
可以使用執行緒示例的setUncaughtExceptionHandler()
方法為某個執行緒設定處理器,也可使用Thread.setDefaultUncaughtExceptionHandler()
為所有執行緒設定預設處理器,程式碼如下:
public class CustomExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("捕獲到執行緒"+t.getName()+",異常:" + e.getMessage());
e.printStackTrace();
}
public static void main(String[] args) {
Thread.setDefaultUncaughtExceptionHandler(new CustomExceptionHandler());
new Thread(() -> {
throw new RuntimeException("test");
}).start();
}
}
如果不設定預設處理器且不為獨立的執行緒設定處理器,那麼該執行緒的處理器就為該執行緒的執行緒組物件--ThreadGroup(因為執行緒組物件實現了Thread.UncaughtExceptionHandler
介面)。
本篇所用全部程式碼:github