一、線程的狀態
1.1 操作系統層面
在操作系統層面有五種狀態:
- 【初始狀態】僅是在語言層面創建了線程對象,還未與操作系統線程關聯
- 【可運行狀態】(就緒狀態)指該線程已經被創建(與操作系統線程關聯),可以由 CPU 調度執行
- 【運行狀態】指獲取了 CPU 時間片運行中的狀態。當 CPU 時間片用完,會從【運行狀態】轉換至【可運行狀態】,會導致線程的上下文切換
- 【阻塞狀態】
- 如果調用了阻塞 API,如 BIO 讀寫文件,這時該線程實際不會用到 CPU,會導致線程上下文切換,進入【阻塞狀態】
- 等 BIO 操作完畢,會由操作系統喚醒阻塞的線程,轉換至【可運行狀態】
- 與【可運行狀態】的區別是,對【阻塞狀態】的線程來說只要它們一直不喚醒,調度器就一直不會考慮調度它們
- 【終止狀態】表示線程已經執行完畢,生命週期已經結束,不會再轉換爲其它狀態
1.2 Java的Thread狀態
Thread的狀態,是一個enum,有六種狀態,如下所示:
public enum State {
/**
* 初始
*/
NEW,
/**
* 可運行
*/
RUNNABLE,
/**
* 阻塞
*/
BLOCKED,
/**
* 等待
*/
WAITING,
/**
* 超時等待
*/
TIMED_WAITING,
/**
* 終止
*/
TERMINATED;
}
- NEW 線程剛被創建,但是還沒有調用 start() 方法
- RUNNABLE 當調用了 start() 方法之後,注意,Java API 層面的 RUNNABLE 狀態涵蓋了 操作系統 層面的【可運行狀態】、【運行狀態】和【阻塞狀態】(由於 BIO 導致的線程阻塞,在 Java 裏無法區分,仍然認爲是可運行)
- BLOCKED , WAITING , TIMED_WAITING 都是 Java API 層面對【阻塞狀態】的細分,在後面第三節(方法與狀態轉換)會講解
- TERMINATED 當線程代碼運行結束
二、Thread的常用方法
2.1 常用方法
方法名 | static | 功能說明 | 注意 |
---|---|---|---|
start() | 啓動一個線程,線程當中運行run()方法中的代碼 | start 方法只是讓線程進入就緒,裏面代碼不一定立刻運行(CPU 的時間片還沒分給它)。每個線程對象的start方法只能調用一次,如果調用了多次會出現IllegalThreadStateException | |
run() | 線程啓動後調用的方法 | 如果在構造 Thread 對象時傳遞了 Runnable 參數,則線程啓動後會調用 Runnable 中的 run 方法,否則默認不執行任何操作。但可以創建 Thread 的子類對象,來覆蓋默認行爲; class ExtendThread extends Thread { @Override public void run() { System.out.println("繼承Thread類方式"); } } |
|
join() | 等待當前線程執行結束 | 在當前執行線程a中,另一個線程b調用該方法,則線程a進入WAITING狀態,直到線程b執行完畢,線程a繼續執行 | |
join(long n) | 等待當前線程運行結束,最多等待 n毫秒 | ||
getId() | 獲取線程長整型的 id | 唯一id | |
getName() | 獲取線程名稱 | ||
setName(String) | 修改線程名稱 | ||
getPriority() | 獲取線程優先級 | ||
setPriority(int) | 修改線程優先級 | java中規定線程優先級是1~10 的整數,較大的優先級能提高該線程被 CPU 調度的機率 | |
getState() | 獲取線程狀態 | Java 中線程狀態是用 6 個 enum 表示,分別爲: NEW RUNNABLE BLOCKED WAITING TIMED_WAITING TERMINATED |
|
isInterrupted() | 判斷是否被打斷 | 不會清除 打斷標記 | |
isAlive() | 判斷線程是否存活(是否運行完畢) | ||
interrupt() | 打斷線程 | 如果被打斷線程正在 sleep,wait,join 會導致被打斷的線程拋出InterruptedException,並清除 打斷標記 ; 如果打斷的正在運行的線程,則會設置 打斷標記 ; park 的線程被打斷,也會設置 打斷標記。 |
|
interrupted() | static | 判斷當前線程是否被打斷 | 會清除 打斷標記 |
currentThread() | static | 獲取當前正在執行的線程 | |
sleep(long n) | static | 讓當前執行的線程休眠n毫秒,休眠時讓出 cpu的時間片給其它線程 | |
yied() | static | 提示線程調度器讓出當前線程對CPU的使用 | 主要是爲了測試和調試 |
2.2 sleep和yied
2.2.1 sleep
- 調用 sleep 會讓當前線程從 Running 進入 Timed Waiting 狀態(阻塞)
- 其它線程可以使用 interrupt 方法打斷正在睡眠的線程,這時 sleep 方法會拋出 InterruptedException
- 睡眠結束後的線程未必會立刻得到執行
- 建議用 TimeUnit 的 sleep 代替 Thread 的 sleep 來獲得更好的可讀性(內部也是Thread.sleep)
TimeUnit.SECONDS.sleep(5);
2.2.2 yied
- 調用 yield 會讓當前線程從 Running 進入 Runnable 就緒狀態,然後調度執行其它線程
- 具體的實現依賴於操作系統的任務調度器
2.3 interrupt 方法詳解
線程的Thread.interrupt()方法是中斷線程,將會設置該線程的中斷狀態,即設置爲true。
其作用僅僅而已,線程關閉還是繼續執行業務進程應該由我們的程序自己進行判斷。
針對不同狀態下的線程進行中斷操作,會有不一樣的結果:
2.3.1 中斷wait() 、join()、sleep()
如果此線程在調用Object類的wait() 、 wait(long)或wait(long, int)方法或join() 、 join(long) 、 join(long, int)被阻塞、 sleep(long)或sleep(long, int) ,此類的方法,則其中斷狀態將被清除並收到InterruptedException 。
以sleep舉例:
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println("打斷狀態:" + Thread.currentThread().isInterrupted());
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("打斷後的狀態:" + Thread.currentThread().isInterrupted());
});
t.start();
t.interrupt();
System.out.println("打斷狀態:" + t.isInterrupted());
}
結果:
打斷狀態:true
打斷狀態:true
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at com.cloud.bssp.thread.InterruptTest.lambda$main$0(InterruptTest.java:18)
at java.lang.Thread.run(Thread.java:748)
2.3.2 中斷正常線程
正常線程將會被設置中斷標記位,我們可以根據該標記位判斷線程如何執行,如下所示:
/**
* 中斷正常線程
*
* @param args
*/
public static void main(String[] args) {
Thread t = new Thread(() -> {
while (true){
if(Thread.currentThread().isInterrupted()){
System.out.println("中斷狀態:" + Thread.currentThread().isInterrupted());
break;
}
}
});
t.start();
t.interrupt();
}
結果:
中斷狀態:true
2.3.3 中斷park線程
不會使中斷狀態清除。
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while (true) {
System.out.println("park");
LockSupport.park();
System.out.println("unpark");
System.out.println("中斷狀態:" + Thread.currentThread().isInterrupted());
if (Thread.currentThread().isInterrupted()) {
break;
}
}
});
t.start();
TimeUnit.SECONDS.sleep(1);
t.interrupt();
}
結果:
park
unpark
中斷狀態:true
如果在park之前,線程已經是中斷狀態了,則會使park失效,如下所示,除了首次park成功能成功,被中斷後,後面的park都失效了:
/**
* 中斷park
*
* @param args
*/
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("打斷狀態:" + Thread.currentThread().isInterrupted());
System.out.println("park..." + i);
LockSupport.park();
}
});
t1.start();
TimeUnit.SECONDS.sleep(1);
t1.interrupt();
}
結果:
打斷狀態:false
park...0
打斷狀態:true
park...1
打斷狀態:true
park...2
打斷狀態:true
park...3
打斷狀態:true
park...4
可以 Thread.interrupted() 方法去除中斷標記:
2.3.5 不推薦使用的方法
方法名稱 | 描述 |
---|---|
stop() | 停止線程運行。不安全的,並將會在未來版本刪除 |
suspend() | 掛起(暫停)線程運行,此方法已被棄用,因爲它本質上容易死鎖 |
resume() | 恢復線程運行。此方法僅用於suspend ,已被棄用,因爲它容易死鎖 |
2.3.4 其他中斷
如果此線程在InterruptibleChannel的 I/O 操作中被阻塞,則通道將關閉,線程的中斷狀態將被設置,並且線程將收到java.nio.channels.ClosedByInterruptException 。
如果該線程在java.nio.channels.Selector被阻塞,則該線程的中斷狀態將被設置,並且它將立即從選擇操作中返回,可能具有非零值,就像調用了選擇器的wakeup方法。
三、方法與狀態轉換
如下圖所示,線的右側表示執行的方法:
下面具體分析方法和狀態轉換,假設有一個線程Thread t:
1.NEW --> RUNNABLE
執行t.start()
2.RUNNABLE <--> WAITING
此種狀態轉換分三種情況:
1)t 線程用 synchronized(obj) 獲取了對象鎖後,調用 obj.wait() 方法時,t 線程從 RUNNABLE --> WAITING
調用 obj.notify() , obj.notifyAll() , t.interrupt() 時:
- 競爭鎖成功,t 線程從 WAITING --> RUNNABLE
- 競爭鎖失敗,t 線程從 WAITING --> BLOCKED
2)當前線程調用 t.join() 方法時,當前線程從 RUNNABLE --> WAITING
注意是當前線程在t 線程對象的監視器上等待
當前線程會等到t執行結束後或調用了當前線程的 interrupt() 時,WAITING --> RUNNABLE。
3)當前線程調用 LockSupport.park() 方法會讓當前線程從 RUNNABLE --> WAITING
調用 LockSupport.unpark(目標線程) 或調用了線程 的 interrupt() ,會讓目標線程從 WAITING --> RUNNABLE
3.RUNNABLE <--> TIMED_WAITING
此種狀態轉換分四種情況:
1) t 線程用 synchronized(obj) 獲取了對象鎖後,調用 obj.wait(long n) 方法時,t 線程從 RUNNABLE --> TIMED_WAITING
t 線程等待時間超過了 n 毫秒,或調用 obj.notify() , obj.notifyAll() , t.interrupt() 時:
- 競爭鎖成功,t 線程從 TIMED_WAITING --> RUNNABLE
- 競爭鎖失敗,t 線程從 TIMED_WAITING --> BLOCKED
2)當前線程調用 t.join(long n) 方法時,當前線程從 RUNNABLE --> TIMED_WAITING
注意是當前線程在t 線程對象的監視器上等待
當前線程等待時間超過了 n 毫秒,或t 線程運行結束,或調用了當前線程的 interrupt() 時,當前線程從TIMED_WAITING --> RUNNABLE
3)當前線程調用 Thread.sleep(long n) ,當前線程從 RUNNABLE --> TIMED_WAITING
當前線程等待時間超過了 n 毫秒,當前線程從 TIMED_WAITING --> RUNNABLE
4)當前線程調用 LockSupport.parkNanos(long nanos) 或 LockSupport.parkUntil(long millis) 時,當前線程從 RUNNABLE --> TIMED_WAITING
調用 LockSupport.unpark(目標線程) 或調用了線程 的 interrupt() ,或是等待超時,會讓目標線程從
TIMED_WAITING--> RUNNABLE
4.RUNNABLE <--> BLOCKED
t 線程用 synchronized(obj) 獲取了對象鎖時如果競爭失敗,從 RUNNABLE --> BLOCKED
持 obj 鎖線程的同步代碼塊執行完畢,會喚醒該對象上所有 BLOCKED 的線程重新競爭,如果其中 t 線程競爭成功,從 BLOCKED --> RUNNABLE ,其它失敗的線程仍然 BLOCKED
5.RUNNABLE <--> TERMINATED
當前線程所有代碼運行完畢,進入 TERMINATED