《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