一澳叉、線程的狀態(tài)
1.1 操作系統(tǒng)層面
在操作系統(tǒng)層面有五種狀態(tài):
- 【初始狀態(tài)】?jī)H是在語(yǔ)言層面創(chuàng)建了線程對(duì)象倔既,還未與操作系統(tǒng)線程關(guān)聯(lián)
- 【可運(yùn)行狀態(tài)】(就緒狀態(tài))指該線程已經(jīng)被創(chuàng)建(與操作系統(tǒng)線程關(guān)聯(lián))斋日,可以由 CPU 調(diào)度執(zhí)行
- 【運(yùn)行狀態(tài)】指獲取了 CPU 時(shí)間片運(yùn)行中的狀態(tài)建瘫。當(dāng) CPU 時(shí)間片用完贯被,會(huì)從【運(yùn)行狀態(tài)】轉(zhuǎn)換至【可運(yùn)行狀態(tài)】噪窘,會(huì)導(dǎo)致線程的上下文切換
- 【阻塞狀態(tài)】
- 如果調(diào)用了阻塞 API,如 BIO 讀寫文件泞遗,這時(shí)該線程實(shí)際不會(huì)用到 CPU惰许,會(huì)導(dǎo)致線程上下文切換,進(jìn)入【阻塞狀態(tài)】
- 等 BIO 操作完畢史辙,會(huì)由操作系統(tǒng)喚醒阻塞的線程汹买,轉(zhuǎn)換至【可運(yùn)行狀態(tài)】
- 與【可運(yùn)行狀態(tài)】的區(qū)別是,對(duì)【阻塞狀態(tài)】的線程來(lái)說只要它們一直不喚醒聊倔,調(diào)度器就一直不會(huì)考慮調(diào)度它們
- 【終止?fàn)顟B(tài)】表示線程已經(jīng)執(zhí)行完畢晦毙,生命周期已經(jīng)結(jié)束,不會(huì)再轉(zhuǎn)換為其它狀態(tài)
1.2 Java的Thread狀態(tài)
Thread的狀態(tài)耙蔑,是一個(gè)enum见妒,有六種狀態(tài),如下所示:
public enum State {
/**
* 初始
*/
NEW,
/**
* 可運(yùn)行
*/
RUNNABLE,
/**
* 阻塞
*/
BLOCKED,
/**
* 等待
*/
WAITING,
/**
* 超時(shí)等待
*/
TIMED_WAITING,
/**
* 終止
*/
TERMINATED;
}
- NEW 線程剛被創(chuàng)建甸陌,但是還沒有調(diào)用 start() 方法
- RUNNABLE 當(dāng)調(diào)用了 start() 方法之后须揣,注意盐股,Java API 層面的 RUNNABLE 狀態(tài)涵蓋了 操作系統(tǒng) 層面的【可運(yùn)行狀態(tài)】、【運(yùn)行狀態(tài)】和【阻塞狀態(tài)】(由于 BIO 導(dǎo)致的線程阻塞耻卡,在 Java 里無(wú)法區(qū)分疯汁,仍然認(rèn)為是可運(yùn)行)
- BLOCKED , WAITING 卵酪, TIMED_WAITING 都是 Java API 層面對(duì)【阻塞狀態(tài)】的細(xì)分涛目,在后面第三節(jié)(方法與狀態(tài)轉(zhuǎn)換)會(huì)講解
- TERMINATED 當(dāng)線程代碼運(yùn)行結(jié)束
二、Thread的常用方法
2.1 常用方法
方法名 | static | 功能說明 | 注意 |
---|---|---|---|
start() | 啟動(dòng)一個(gè)線程凛澎,線程當(dāng)中運(yùn)行run()方法中的代碼 | start 方法只是讓線程進(jìn)入就緒霹肝,里面代碼不一定立刻運(yùn)行(CPU 的時(shí)間片還沒分給它)。每個(gè)線程對(duì)象的start方法只能調(diào)用一次塑煎,如果調(diào)用了多次會(huì)出現(xiàn)IllegalThreadStateException | |
run() | 線程啟動(dòng)后調(diào)用的方法 | 如果在構(gòu)造 Thread 對(duì)象時(shí)傳遞了 Runnable 參數(shù)沫换,則線程啟動(dòng)后會(huì)調(diào)用 Runnable 中的 run 方法,否則默認(rèn)不執(zhí)行任何操作最铁。但可以創(chuàng)建 Thread 的子類對(duì)象讯赏,來(lái)覆蓋默認(rèn)行為; class ExtendThread extends Thread { ? ?@Override ?? public void run() { ????System.out.println("繼承Thread類方式"); ???} } |
|
join() | 等待當(dāng)前線程執(zhí)行結(jié)束 | 在當(dāng)前執(zhí)行線程a中,另一個(gè)線程b調(diào)用該方法冷尉,則線程a進(jìn)入WAITING狀態(tài)漱挎,直到線程b執(zhí)行完畢,線程a繼續(xù)執(zhí)行 原理:調(diào)用者輪詢檢查線程 alive 狀態(tài) 等價(jià)于下面的代碼: synchronized (t1) { ???// 調(diào)用者線程進(jìn)入 t1 的 waitSet 等待, 直到 t1 運(yùn)行結(jié)束 ??????while (t1.isAlive()) { ??????t1.wait(0); ???} } |
|
join(long n) | 等待當(dāng)前線程運(yùn)行結(jié)束,最多等待 n毫秒 | ||
getId() | 獲取線程長(zhǎng)整型的 id | 唯一id | |
getName() | 獲取線程名稱 | ||
setName(String) | 修改線程名稱 | ||
getPriority() | 獲取線程優(yōu)先級(jí) | ||
setPriority(int) | 修改線程優(yōu)先級(jí) | java中規(guī)定線程優(yōu)先級(jí)是1~10 的整數(shù)雀哨,較大的優(yōu)先級(jí)能提高該線程被 CPU 調(diào)度的機(jī)率 | |
getState() | 獲取線程狀態(tài) | Java 中線程狀態(tài)是用 6 個(gè) enum 表示磕谅,分別為: NEW RUNNABLE BLOCKED WAITING TIMED_WAITING TERMINATED |
|
isInterrupted() | 判斷是否被打斷 | 不會(huì)清除 打斷標(biāo)記 | |
isAlive() | 判斷線程是否存活(是否運(yùn)行完畢) | ||
interrupt() | 打斷線程 | 如果被打斷線程正在 sleep,wait雾棺,join 會(huì)導(dǎo)致被打斷的線程拋出InterruptedException膊夹,并清除 打斷標(biāo)記 ; 如果打斷的正在運(yùn)行的線程捌浩,則會(huì)設(shè)置 打斷標(biāo)記 放刨; park 的線程被打斷,也會(huì)設(shè)置 打斷標(biāo)記尸饺。 |
|
interrupted() | static | 判斷當(dāng)前線程是否被打斷 | 會(huì)清除 打斷標(biāo)記 |
currentThread() | static | 獲取當(dāng)前正在執(zhí)行的線程 | |
sleep(long n) | static | 讓當(dāng)前執(zhí)行的線程休眠n毫秒进统,休眠時(shí)讓出 cpu的時(shí)間片給其它線程 | |
yied() | static | 提示線程調(diào)度器讓出當(dāng)前線程對(duì)CPU的使用 | 主要是為了測(cè)試和調(diào)試 |
2.2 sleep和yied
2.2.1 sleep
- 調(diào)用 sleep 會(huì)讓當(dāng)前線程從 Running 進(jìn)入 Timed Waiting 狀態(tài)(阻塞)
- 其它線程可以使用 interrupt 方法打斷正在睡眠的線程,這時(shí) sleep 方法會(huì)拋出 InterruptedException
- 睡眠結(jié)束后的線程未必會(huì)立刻得到執(zhí)行
- 建議用 TimeUnit 的 sleep 代替 Thread 的 sleep 來(lái)獲得更好的可讀性(內(nèi)部也是Thread.sleep)
TimeUnit.SECONDS.sleep(5);
2.2.2 yied
- 調(diào)用 yield 會(huì)讓當(dāng)前線程從 Running 進(jìn)入 Runnable 就緒狀態(tài)浪听,然后調(diào)度執(zhí)行其它線程
- 具體的實(shí)現(xiàn)依賴于操作系統(tǒng)的任務(wù)調(diào)度器
2.3 interrupt 方法詳解
線程的Thread.interrupt()方法是中斷線程螟碎,將會(huì)設(shè)置該線程的中斷狀態(tài),即設(shè)置為true馋辈。
其作用僅僅而已抚芦,線程關(guān)閉還是繼續(xù)執(zhí)行業(yè)務(wù)進(jìn)程應(yīng)該由我們的程序自己進(jìn)行判斷。
針對(duì)不同狀態(tài)下的線程進(jìn)行中斷操作迈螟,會(huì)有不一樣的結(jié)果:
2.3.1 中斷wait() 叉抡、join()、sleep()
如果此線程在調(diào)用Object類的wait() 答毫、 wait(long)或wait(long, int)方法或join() 褥民、 join(long) 、 join(long, int)被阻塞洗搂、 sleep(long)或sleep(long, int) 消返,此類的方法,則其中斷狀態(tài)將被清除并收到InterruptedException 耘拇。
以sleep舉例:
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println("打斷狀態(tài):" + Thread.currentThread().isInterrupted());
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("打斷后的狀態(tài):" + Thread.currentThread().isInterrupted());
});
t.start();
t.interrupt();
System.out.println("打斷狀態(tài):" + t.isInterrupted());
}
結(jié)果:
打斷狀態(tài):true
打斷狀態(tài):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 中斷正常線程
正常線程將會(huì)被設(shè)置中斷標(biāo)記位撵颊,我們可以根據(jù)該標(biāo)記位判斷線程如何執(zhí)行,如下所示:
/**
* 中斷正常線程
*
* @param args
*/
public static void main(String[] args) {
Thread t = new Thread(() -> {
while (true){
if(Thread.currentThread().isInterrupted()){
System.out.println("中斷狀態(tài):" + Thread.currentThread().isInterrupted());
break;
}
}
});
t.start();
t.interrupt();
}
結(jié)果:
中斷狀態(tài):true
2.3.3 中斷park線程
不會(huì)使中斷狀態(tài)清除惫叛。
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("中斷狀態(tài):" + Thread.currentThread().isInterrupted());
if (Thread.currentThread().isInterrupted()) {
break;
}
}
});
t.start();
TimeUnit.SECONDS.sleep(1);
t.interrupt();
}
結(jié)果:
park
unpark
中斷狀態(tài):true
如果在park之前倡勇,線程已經(jīng)是中斷狀態(tài)了,則會(huì)使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("打斷狀態(tài):" + Thread.currentThread().isInterrupted());
System.out.println("park..." + i);
LockSupport.park();
}
});
t1.start();
TimeUnit.SECONDS.sleep(1);
t1.interrupt();
}
結(jié)果:
打斷狀態(tài):false
park...0
打斷狀態(tài):true
park...1
打斷狀態(tài):true
park...2
打斷狀態(tài):true
park...3
打斷狀態(tài):true
park...4
可以 Thread.interrupted() 方法去除中斷標(biāo)記:
2.3.5 不推薦使用的方法
方法名稱 | 描述 |
---|---|
stop() | 停止線程運(yùn)行。不安全的,并將會(huì)在未來(lái)版本刪除 |
suspend() | 掛起(暫停)線程運(yùn)行逼龟,此方法已被棄用典徘,因?yàn)樗举|(zhì)上容易死鎖 |
resume() | 恢復(fù)線程運(yùn)行。此方法僅用于suspend 预皇,已被棄用损敷,因?yàn)樗菀姿梨i |
2.3.4 其他中斷
如果此線程在InterruptibleChannel的 I/O 操作中被阻塞,則通道將關(guān)閉深啤,線程的中斷狀態(tài)將被設(shè)置拗馒,并且線程將收到j(luò)ava.nio.channels.ClosedByInterruptException 。
如果該線程在java.nio.channels.Selector被阻塞溯街,則該線程的中斷狀態(tài)將被設(shè)置诱桂,并且它將立即從選擇操作中返回,可能具有非零值呈昔,就像調(diào)用了選擇器的wakeup方法挥等。
三、方法與狀態(tài)轉(zhuǎn)換
如下圖所示堤尾,線的右側(cè)表示執(zhí)行的方法:
下面具體分析方法和狀態(tài)轉(zhuǎn)換肝劲,假設(shè)有一個(gè)線程Thread t:
1.NEW --> RUNNABLE
執(zhí)行t.start()
2.RUNNABLE <--> WAITING
此種狀態(tài)轉(zhuǎn)換分三種情況:
1)t 線程用 synchronized(obj) 獲取了對(duì)象鎖后,調(diào)用 obj.wait() 方法時(shí),t 線程從 RUNNABLE --> WAITING
調(diào)用 obj.notify() 辞槐, obj.notifyAll() 掷漱, t.interrupt() 時(shí):
- 競(jìng)爭(zhēng)鎖成功,t 線程從 WAITING --> RUNNABLE
- 競(jìng)爭(zhēng)鎖失敗榄檬,t 線程從 WAITING --> BLOCKED
2)當(dāng)前線程調(diào)用 t.join() 方法時(shí)卜范,當(dāng)前線程從 RUNNABLE --> WAITING
注意是當(dāng)前線程在t 線程對(duì)象的監(jiān)視器上等待
當(dāng)前線程會(huì)等到t執(zhí)行結(jié)束后或調(diào)用了當(dāng)前線程的 interrupt() 時(shí),WAITING --> RUNNABLE鹿榜。
3)當(dāng)前線程調(diào)用 LockSupport.park() 方法會(huì)讓當(dāng)前線程從 RUNNABLE --> WAITING
調(diào)用 LockSupport.unpark(目標(biāo)線程) 或調(diào)用了線程 的 interrupt() 海雪,會(huì)讓目標(biāo)線程從 WAITING --> RUNNABLE
3.RUNNABLE <--> TIMED_WAITING
此種狀態(tài)轉(zhuǎn)換分四種情況:
1) t 線程用 synchronized(obj) 獲取了對(duì)象鎖后,調(diào)用 obj.wait(long n) 方法時(shí)舱殿,t 線程從 RUNNABLE --> TIMED_WAITING
t 線程等待時(shí)間超過了 n 毫秒奥裸,或調(diào)用 obj.notify() , obj.notifyAll() 沪袭, t.interrupt() 時(shí):
- 競(jìng)爭(zhēng)鎖成功湾宙,t 線程從 TIMED_WAITING --> RUNNABLE
- 競(jìng)爭(zhēng)鎖失敗,t 線程從 TIMED_WAITING --> BLOCKED
2)當(dāng)前線程調(diào)用 t.join(long n) 方法時(shí)枝恋,當(dāng)前線程從 RUNNABLE --> TIMED_WAITING
注意是當(dāng)前線程在t 線程對(duì)象的監(jiān)視器上等待
當(dāng)前線程等待時(shí)間超過了 n 毫秒创倔,或t 線程運(yùn)行結(jié)束,或調(diào)用了當(dāng)前線程的 interrupt() 時(shí)焚碌,當(dāng)前線程從TIMED_WAITING --> RUNNABLE
3)當(dāng)前線程調(diào)用 Thread.sleep(long n) 畦攘,當(dāng)前線程從 RUNNABLE --> TIMED_WAITING
當(dāng)前線程等待時(shí)間超過了 n 毫秒,當(dāng)前線程從 TIMED_WAITING --> RUNNABLE
4)當(dāng)前線程調(diào)用 LockSupport.parkNanos(long nanos) 或 LockSupport.parkUntil(long millis) 時(shí)十电,當(dāng)前線程從 RUNNABLE --> TIMED_WAITING
調(diào)用 LockSupport.unpark(目標(biāo)線程) 或調(diào)用了線程 的 interrupt() 知押,或是等待超時(shí),會(huì)讓目標(biāo)線程從
TIMED_WAITING--> RUNNABLE
4.RUNNABLE <--> BLOCKED
t 線程用 synchronized(obj) 獲取了對(duì)象鎖時(shí)如果競(jìng)爭(zhēng)失敗鹃骂,從 RUNNABLE --> BLOCKED
持 obj 鎖線程的同步代碼塊執(zhí)行完畢台盯,會(huì)喚醒該對(duì)象上所有 BLOCKED 的線程重新競(jìng)爭(zhēng),如果其中 t 線程競(jìng)爭(zhēng)成功畏线,從 BLOCKED --> RUNNABLE 静盅,其它失敗的線程仍然 BLOCKED
5.RUNNABLE <--> TERMINATED
當(dāng)前線程所有代碼運(yùn)行完畢,進(jìn)入 TERMINATED