java线程主要状态及转换_Java线程状态转换及控制
線程的狀態(tài)(系統(tǒng)層面)
一個(gè)線程被創(chuàng)建后就進(jìn)入了線程的生命周期。在線程的生命周期中,共包括新建(New)、就緒(Runnable)、運(yùn)行(Running)、阻塞(Blocked)和死亡(Dead)這五種狀態(tài)。當(dāng)線程啟動(dòng)以后,CPU需要在多個(gè)線程之間切換,所以線程也會(huì)隨之在運(yùn)行、阻塞、就緒這幾種狀態(tài)之間切換。
線程的狀態(tài)轉(zhuǎn)換如圖:
當(dāng)一個(gè)正在運(yùn)行的線程遇到如下情況時(shí),線程會(huì)從運(yùn)行態(tài)轉(zhuǎn)為阻塞態(tài):
①?線程調(diào)用sleep、join等方法。
②?線程調(diào)用了一個(gè)阻塞式IO方法。
③?線程試圖獲得一個(gè)同步監(jiān)視器,但是該監(jiān)視器正在被其他線程持有。
④ 線程在等待某個(gè) notify 通知。
⑤ 程序調(diào)用了線程的suspend方法將該線程掛起。
當(dāng)線程被阻塞后,其他線程就有機(jī)會(huì)獲得CPU資源而被執(zhí)行。當(dāng)上述導(dǎo)致線程被阻塞的因素解除后,線程會(huì)回到就緒狀態(tài)等待處理機(jī)調(diào)度而被執(zhí)行。
當(dāng)一個(gè)線程執(zhí)行結(jié)束后,該線程進(jìn)入死亡狀態(tài)。
有以下3種方式可結(jié)束一個(gè)線程:
① run 方法執(zhí)行完畢。
② 線程拋出一個(gè)異常或錯(cuò)誤,而該異常或錯(cuò)誤未被捕獲。
③ 調(diào)用線程的 stop方法結(jié)束該線程。(不推薦使用)
線程的控制
Thread類中提供了一些控制線程的方法,通過(guò)這些方法可以輕松地控制一個(gè)線程的執(zhí)行和運(yùn)行狀態(tài),以達(dá)到程序的預(yù)期效果。
join 方法
如果線程A調(diào)用了線程B的join方法,線程A將被阻塞,等待線程B執(zhí)行完畢后線程A才會(huì)被執(zhí)行。這里需要注意一點(diǎn)的是,join方法必須在線程B的start方法調(diào)用之后調(diào)用才有意義。join方法的主要作用就是實(shí)現(xiàn)線程間的同步,它可以使線程之間的并行執(zhí)行變?yōu)榇袌?zhí)行。
join 方法有以下3種重載形式:
① join(): 等待被join的線程執(zhí)行完成。
② join(long millis): 等待被join 的線程的時(shí)間為 millis 毫秒,如果該線程在millis 毫秒內(nèi)未結(jié)束,則不再等待。
③ join(long millis,int nanos): 等待被join的線程的時(shí)間最長(zhǎng)為 millis 毫秒加上nanos微秒。
public class JoinThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public class TestThreadState {
public static void main(String[] args) {
// 創(chuàng)建要加入當(dāng)前線程的線程,并啟動(dòng)
JoinThread j1 = new JoinThread();
j1.start();
// 加入當(dāng)前線程,阻塞當(dāng)前線程,直到加入線程執(zhí)行完畢
try {
j1.join();
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
我們定義了一個(gè)JoinThread類,它繼承了Thread類,這是我們要加入的線程類。
在main方法中,我們創(chuàng)建了JoinThread線程,并把它加入到當(dāng)前線程(主線程)中,并沒(méi)有指定當(dāng)前線程等待的時(shí)間,所以會(huì)一直阻塞當(dāng)前線程,直到JoinThread線程的run方法執(zhí)行完畢,才會(huì)繼續(xù)執(zhí)行當(dāng)前線程。
sleep 方法
當(dāng)線程A調(diào)用了 sleep方法,則線程A將被阻塞,直到指定睡眠的時(shí)間到達(dá)后,線程A才會(huì)重新被喚起,進(jìn)入就緒狀態(tài)。
sleep方法有以下2種重載形式:
① sleep(long millis):讓當(dāng)前正在執(zhí)行的線程暫停millis毫秒,該線程進(jìn)入阻塞狀態(tài)。
② sleep(long mills,long nanos):讓當(dāng)前正在執(zhí)行的線程暫停 millis 毫秒加上 nanos微秒。
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
try {
Thread.sleep(1000); // 阻塞當(dāng)前線程1s
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
這段代碼中沒(méi)有創(chuàng)建其他線程,只有當(dāng)前線程存在,也就是執(zhí)行main函數(shù)的主線程。for循環(huán)中每打印一次線程名稱,主線程就會(huì)被sleep方法阻塞1s,然后進(jìn)入就緒狀態(tài),重新等待被調(diào)到,實(shí)現(xiàn)了線程的控制。
yield 方法
當(dāng)線程A調(diào)用了yield方法,它可以暫時(shí)放棄處理器,但是線程A不會(huì)被阻塞,而是進(jìn)入就緒狀態(tài)。
public class YieldThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
// 主動(dòng)放棄
Thread.yield();
}
}
}
我們自定義了一個(gè)線程類YieldThread,在run方法中定義了一個(gè)for循環(huán),for循環(huán)中每打印一次線程名稱,就會(huì)調(diào)用一下yield方法,主動(dòng)放棄CUP讓給其它有相同優(yōu)先級(jí)或更高優(yōu)先級(jí)的線程,自己進(jìn)入就緒狀態(tài),等待被CPU調(diào)度。
設(shè)置線程的優(yōu)先級(jí)
每個(gè)線程都有自己的優(yōu)先級(jí),默認(rèn)情況下線程的優(yōu)先級(jí)都與創(chuàng)建該線程的父線程的優(yōu)先級(jí)相回。同時(shí)Thread類提供了setPriority(int priority) 和getPriority()方法設(shè)置和返回指定線程的優(yōu)先級(jí)。參數(shù)priority是一個(gè)整型數(shù)據(jù),用以指定線程的優(yōu)先級(jí)。priority 的取值范圍是1-10,默認(rèn)值為5,也可以使用Thread類提供的三個(gè)靜態(tài)常量設(shè)置線程的優(yōu)先級(jí)。
① MAX_PRIORITY:最高優(yōu)先級(jí),其值為10。
② MIN_PRIORITY:最低優(yōu)先級(jí),其值為1。
③ NORM_PRIORITY:普通優(yōu)先級(jí),其值為5。
public class TestThreadPriority {
public static void main(String[] args) {
// 線程優(yōu)先級(jí)
ThreadPriority p1 = new ThreadPriority();
p1.setName("p1");
ThreadPriority p2 = new ThreadPriority();
p2.setName("p2");
ThreadPriority p3 = new ThreadPriority();
p3.setName("p3");
p1.setPriority(1);
p3.setPriority(10);
p1.start();
p2.start();
p3.start();
}
}
我們創(chuàng)建了三個(gè)線程p1、p2、p3,設(shè)置了p1的優(yōu)先級(jí)為1,p3的優(yōu)先級(jí)為10,并沒(méi)有設(shè)置p2的,所以p2的優(yōu)先級(jí)默認(rèn)是5。優(yōu)先級(jí)越高,表示獲取cup的機(jī)會(huì)越多,注意此處說(shuō)的是機(jī)會(huì),所以高優(yōu)先級(jí)的線程并不是一定先于低優(yōu)先級(jí)的線程被CPU調(diào)度,只是機(jī)會(huì)更大而已。
sleep方法和wait方法的區(qū)別是什么?
sleep?方法是Thread類的一個(gè)靜態(tài)方法,其作用是使運(yùn)行中的線程暫時(shí)停止指定的毫秒數(shù),從而該線程進(jìn)入阻塞狀態(tài)并讓出處理器,將執(zhí)行的機(jī)會(huì)讓給其他線程。但是這個(gè)過(guò)程中監(jiān)控狀態(tài)始終保持,當(dāng)sleep的時(shí)間到了之后線程會(huì)自動(dòng)恢復(fù)。
wait 方法是Object類的方法,它是用來(lái)實(shí)現(xiàn)線程同步的。當(dāng)調(diào)用某個(gè)對(duì)象的wait方法后,當(dāng)前線程會(huì)被阻塞并釋放同步鎖,直到其他線程調(diào)用了該對(duì)象的?notify?方法或者?notifyAll?方法來(lái)喚醒該線程。所以?wait?方法和?notify(或notifyAll)應(yīng)當(dāng)成對(duì)出現(xiàn)以保證線程間的協(xié)調(diào)運(yùn)行。
sleep方法和yield方法的區(qū)別是什么?
① sleep方法暫停當(dāng)前線程后,會(huì)給其他線程執(zhí)行機(jī)會(huì)而不會(huì)考慮其他線程的優(yōu)先級(jí)。但是yield方法只會(huì)給優(yōu)先級(jí)相同或者優(yōu)先級(jí)更高的線程執(zhí)行機(jī)會(huì)。
② sleep方法執(zhí)行后線程會(huì)進(jìn)入阻塞狀態(tài),而執(zhí)行了yield方法后,當(dāng)前線程會(huì)進(jìn)入就緒狀態(tài)。
③ 由于sleep方法的聲明拋出了 InterruptedException 異常,所以在調(diào)用sleep方法時(shí)需要catch 該異常或拋出該異常,而yield 方法沒(méi)有聲明拋出異常。
④ sleep 方法比yield 方法具有更好的可移植性。
補(bǔ)充一下sleep、yield、join和wait的差異:
①?sleep、join、yield時(shí)并不釋放對(duì)象鎖資源,在wait操作時(shí)會(huì)釋放對(duì)象資源,wait在被notify/notifyAll喚醒時(shí),重新去搶奪獲取對(duì)象鎖資源。
②?sleep、join、yield可以在任何地方使用,而wait,notify,notifyAll只能在同步控制方法或者同步控制塊中使用。
③?調(diào)用wait會(huì)立即釋放鎖,進(jìn)入等待隊(duì)列,但是notify()不會(huì)立刻釋放sycronized(obj)中的對(duì)象鎖,必須要等notify()所在線程執(zhí)行完sycronized(obj)同步塊中的所有代碼才會(huì)釋放這把鎖,然后供等待的線程來(lái)?yè)寠Z對(duì)象鎖。
Java中為什么不建議使用stop和suspend方法終止線程?
在Java中可以使用stop 方法停止一個(gè)線程,使該線程進(jìn)入死亡狀態(tài)。但是使用這種方法結(jié)束一個(gè)線程是不安全的,在編寫程序時(shí)應(yīng)當(dāng)禁止使用這種方法。
之所以說(shuō)stop方法是線程不安全的,是因?yàn)橐坏┱{(diào)用了Thread.stop()方法,工作線程將拋出一個(gè)ThreadDeath的異常,這會(huì)導(dǎo)致run方法結(jié)束執(zhí)行,而且結(jié)束的點(diǎn)是不可控的,也就是說(shuō),它可能執(zhí)行到run方法的任何一個(gè)位置就突然終止了。同時(shí)它還會(huì)釋放掉該線程所持有的鎖,這樣其他因?yàn)檎?qǐng)求該鎖對(duì)象而被阻塞的線程就會(huì)獲得鎖對(duì)象而繼續(xù)執(zhí)行下去。一般情況下,加鎖的目的是保護(hù)數(shù)據(jù)的一致性,然而如果在調(diào)用Thread.stop()后線程立即終止,那么被保護(hù)數(shù)據(jù)就有可能出現(xiàn)不一致的情況(數(shù)據(jù)的狀態(tài)不可預(yù)知)。同時(shí),該線程所持有的鎖突然被釋放,其他線程獲得同步鎖后可以進(jìn)入臨界區(qū)使用這些被破壞的數(shù)據(jù),這將有可能導(dǎo)致一些很奇怪的應(yīng)用程序錯(cuò)誤發(fā)生,而且這種錯(cuò)誤非常難以debug.所以在這里再次重申,不要試圖用stop 方法結(jié)束一個(gè)線程。
suspend方法可以阻塞一個(gè)線程,然而該線程雖然被阻塞,但它仍然持有之前獲得的鎖,這樣其他任何線程都不能訪問(wèn)相同鎖對(duì)象保護(hù)的資源,除非被阻塞的線程被重新恢復(fù)。如果此時(shí)只有一個(gè)線程能夠恢復(fù)這個(gè)被suspend的線程,但前提是先要訪問(wèn)被該線程鎖定的臨界資源。這樣便產(chǎn)生了死鎖。所以在編寫程序時(shí),應(yīng)盡量避免使用suspend,如確實(shí)需要阻塞一個(gè)線程的運(yùn)行,最好使用wait方法,這樣既可以阻塞掉當(dāng)前正在執(zhí)行的線程,同時(shí)又使得該線程不至于陷入死鎖。
用一句話說(shuō)就是:stop方法是線程不安全的,可能產(chǎn)生不可預(yù)料的結(jié)果;suspend方法可能導(dǎo)致死鎖。
如何終止一個(gè)線程?
在Java?中不推薦使用stop方法和suspend方法終止一個(gè)線程,因?yàn)槟鞘遣话踩?#xff0c;那么要怎樣終止一個(gè)線程呢?
方法一:使用退出標(biāo)志
正常情況下,當(dāng)Thread?或?Runnable?類的run方法執(zhí)行完畢后該線程即可結(jié)束,但是有些情況下run方法可能永遠(yuǎn)都不會(huì)停止,例如,在服務(wù)端程序中使用線程監(jiān)聽(tīng)客戶端請(qǐng)求,或者執(zhí)行其他需要循環(huán)處理的任務(wù)。這時(shí)如果希望有機(jī)會(huì)終止該線程,可將執(zhí)行的任務(wù)放在一個(gè)循環(huán)中(例如?while循環(huán)),并設(shè)置一個(gè)boolean型的循環(huán)結(jié)束的標(biāo)志。如果想使?while?循環(huán)在某一特定條件下退出,就可以通過(guò)設(shè)置這個(gè)標(biāo)志為true或false?來(lái)控制?while?循環(huán)是否退出。這樣將線程結(jié)束的控制邏輯與線程本身邏輯結(jié)合在一起,可以保證線程安全可控地結(jié)束。
讓我們來(lái)看一看案例:
public class TestQuitSign {
// 退出標(biāo)志
public static volatile boolean quitFlag = false;
// 退出標(biāo)志:針對(duì)運(yùn)行時(shí)的線程
public static void main(String[] args) {
// 線程一:每隔一秒,打印一條信息,當(dāng)quitFlag為true時(shí)結(jié)束run方法。
new Thread() {
public void run() {
System.out.println("thread start...");
while (!quitFlag) {
try {
Thread.sleep(1000);
} catch (Exception e) {
}
System.out.println("thread running...");
}
System.out.println("thread end...");
}
}.start();
// 線程二:等待三秒,設(shè)置quitFlag為true,終止線程一。
new Thread() {
public void run() {
try {
Thread.sleep(3000);
} catch (Exception e) {
// TODO: handle exception
}
quitFlag = true;
}
}.start();
}
}
在上面這段程序中的main方法里創(chuàng)建了兩個(gè)線程,第一個(gè)線程的run方法中有一個(gè)while循環(huán),該循環(huán)通過(guò)boolean型變量quitFlag控制其是否結(jié)束。因?yàn)樽兞縬uitFlag的初始值為false,所以如果不修改該變量,第一個(gè)線程中的run方法將不會(huì)停止,也就是說(shuō),第一個(gè)線程將永遠(yuǎn)不會(huì)終止,并且每隔1s在屏幕上打印出一條字符串。第二個(gè)線程的作用是通過(guò)修改變量quitFlag來(lái)終止第一個(gè)線程。在第二個(gè)線程的run方法中首先將線程阻塞3s,然后將quitFlag置為true.因?yàn)樽兞縬uitFlag是同一進(jìn)程中兩個(gè)線程共享的變量,所以可以通過(guò)修改quitFlag的值來(lái)控制第一個(gè)線程的執(zhí)行。當(dāng)變量quitFlag被置為true,第一個(gè)線程的while循環(huán)就可以終止,所以run方法就能執(zhí)行完畢,從而安全退出第一個(gè)線程。
注意,boolean?型變量?quitFlag 被聲明為?volatile,volatile?會(huì)保證變量在一個(gè)線程中的每一步操作在另一個(gè)線程中都是可見(jiàn)的,所以這樣可以確保將?quitFlag 置為true?后可以安全退出第一個(gè)線程。
方法二:使用?interrupt方法
使用退出線程標(biāo)志的方法終止一個(gè)線程存在一定的局限性,主要的限制就是這種方法只對(duì)運(yùn)行中的線程起作用,如果該線程被阻塞(例如,調(diào)用了?Thread.join()方法或者Thread.sleep()方法等)而處于不可運(yùn)行的狀態(tài)時(shí),則退出線程標(biāo)志的方法將不會(huì)起作用。
在這種情況下,可以使用Thread?提供的?interrupt()方法終止一個(gè)線程。因?yàn)樵摲椒m然不會(huì)中斷一個(gè)正在運(yùn)行的線程,但是它可以使一個(gè)被阻塞的線程拋出一個(gè)中斷異常,從而使線程提前結(jié)束阻塞狀態(tài),然后通過(guò)catch塊捕獲該異常,從而安全地結(jié)束該線程。
我們來(lái)看看下面的例子:
public class TestInterrupt {
// Interrupt方法: 針對(duì)阻塞狀態(tài)的線程
public static void main(String[] args) throws InterruptedException{
// 創(chuàng)建線程
Thread thread = new Thread() {
public void run() {
System.out.println("thread start...");
try {
Thread.sleep(10000);
} catch (InterruptedException e) { // 捕獲中斷異常
e.printStackTrace();
}
System.out.println("thread end...");
}
};
// 啟動(dòng)線程
thread.start();
// 主線程等待1秒,拋出一個(gè)中斷信號(hào)
Thread.sleep(1000);
thread.interrupt();
}
}
在上面這段程序中的main方法里創(chuàng)建了一個(gè)線程,在該線程的?run?方法中調(diào)用?sleep?函數(shù)將該線程阻塞10s.然后調(diào)用Thread?類的?start?方法啟動(dòng)該線程,該線程剛剛被啟動(dòng)就進(jìn)入阻塞狀態(tài)。主線程等待1s后調(diào)用thread.interrupt()拋出一個(gè)中斷信號(hào),在run方法中的catch會(huì)正常捕獲到這個(gè)中斷信號(hào),這樣被阻塞的該線程就會(huì)提前退出阻塞狀態(tài),不需要等待10s線程thread 就會(huì)被提前終止。
上述方法主要針對(duì)當(dāng)前線程調(diào)用了Thread.join()或者 Thread.sleep()等方法而被阻塞時(shí)終止該線程。如果一個(gè)線程被I/O阻塞,則無(wú)法通過(guò)thread.interrupt()拋出一個(gè)中斷信號(hào)而離開(kāi)阻塞狀態(tài)。這時(shí)可推而廣之,觸發(fā)一個(gè)與當(dāng)前I/O0阻塞相關(guān)的異常,使其退出I/O阻塞,然后通過(guò)catch 塊捕獲該異常,從而安全地結(jié)束該線程。
線程的狀態(tài)(JVM層面)
我們?cè)谏厦嬗懻摰木€程狀態(tài)是從操作系統(tǒng)層面來(lái)看的,這樣看比較直觀,也容易理解,也是一個(gè)線程在操作系統(tǒng)中真實(shí)狀態(tài)的體現(xiàn)。下面我們來(lái)看看Java 中線程的狀態(tài)及轉(zhuǎn)換。
Java 線程狀態(tài)
在Java中線程的狀態(tài)有6種,我們來(lái)看一看JDK 1.8幫助文檔中的說(shuō)明:
我們可以看到幫助文檔中的最后一行,這些狀態(tài)是不反映任何操作系統(tǒng)線程狀態(tài)的JVM層面的狀態(tài)。我們來(lái)具體看一看這六種狀態(tài):
NEW:初始狀態(tài),線程被創(chuàng)建,但是還沒(méi)有調(diào)用 start 方法。
RUNNABLED:運(yùn)行狀態(tài),JAVA 線程把操作系統(tǒng)中的就緒和運(yùn)行兩種狀態(tài)統(tǒng)稱為“運(yùn)行狀態(tài)”。
BLOCKED:阻塞狀態(tài),表示線程進(jìn)入等待狀態(tài),也就是線程因?yàn)槟撤N原因放棄了 CPU 使用權(quán),阻塞也分為幾種情況 :
等待阻塞:運(yùn)行的線程執(zhí)行了?Thread.sleep?、wait()、?join()?等方法JVM 會(huì)把當(dāng)前線程設(shè)置為等待狀態(tài),當(dāng) sleep 結(jié)束、join 線程終止或者wait線程被喚醒后,該線程從等待狀態(tài)進(jìn)入到阻塞狀態(tài),重新?lián)屨兼i后進(jìn)行線程恢復(fù);
同步阻塞:運(yùn)行的線程在獲取對(duì)象的同步鎖時(shí),若該同步鎖被其他線程鎖占用了,那么jvm會(huì)把當(dāng)前的線程放入到鎖池中 ;
其他阻塞:發(fā)出了 I/O請(qǐng)求時(shí),JVM 會(huì)把當(dāng)前線程設(shè)置為阻塞狀態(tài),當(dāng) I/O處理完畢則線程恢復(fù);
WAITING:等待狀態(tài),沒(méi)有超時(shí)時(shí)間,要被其他線程喚醒或者有其它的中斷操作;
執(zhí)行 wait()
執(zhí)行 join()
執(zhí)行 LockSupport.park()
TIME_WAITING:超時(shí)等待狀態(tài),超時(shí)以后自動(dòng)返回;
執(zhí)行 sleep(long)
執(zhí)行 wait(long)、join(long)
執(zhí)行 LockSupport.parkNanos(long)、LockSupport.parkUntil(long)
TERMINATED:終止?fàn)顟B(tài),表示當(dāng)前線程執(zhí)行完畢 。
Java 線程狀態(tài)轉(zhuǎn)換
總結(jié)
操作系統(tǒng)層面:
有5個(gè)狀態(tài),分別是:New(新建)、Runnable(就緒)、Running(運(yùn)行)、Blocked(阻塞)、Dead(死亡)。
JVM層面:
有6個(gè)狀態(tài),分別是:NEW(新建)、RUNNABLE(運(yùn)行)、BLOCKED(阻塞)、WAITING(等待)、TIMED_WAITING(超時(shí)等待)、TERMINATED(終止)。
主要由這幾個(gè)方法來(lái)控制:sleep、join、yield、wait、notify以及notifyALL。
總結(jié)
以上是生活随笔為你收集整理的java线程主要状态及转换_Java线程状态转换及控制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 带括号的计算器 java_【福利】jav
- 下一篇: java自动化初始变量_Java自动化测