Java 之 線程狀態(tài)和調(diào)度
線程的狀態(tài)
NEW
創(chuàng)建后尚未啟動(dòng)的線程處于這個(gè)狀態(tài)侨赡。
意思是這個(gè)線程沒(méi)有被start()啟動(dòng)于个,或者說(shuō)還根本不是一個(gè)真正意義上的線程换帜,從本質(zhì)上講這只是創(chuàng)建了一個(gè)Java外殼驮瞧,還沒(méi)有真正的線程來(lái)運(yùn)行粪摘。
不代表調(diào)用了start()叹括,狀態(tài)就立即改變算墨,中間還有一些步驟,如果在這個(gè)啟動(dòng)的過(guò)程中有另一個(gè)線程來(lái)獲取它的狀態(tài)汁雷,其實(shí)是不確定的净嘀,要看那些中間步驟是否已經(jīng)完成了。
RUNNABLE
RUNNABLE狀態(tài)包括了操作系統(tǒng)線程狀態(tài)中的Running和Ready摔竿,也就是處于此狀態(tài)的線程可能正在運(yùn)行面粮,也可能正在等待系統(tǒng)資源,如等待CPU為它分配時(shí)間片继低,如等待網(wǎng)絡(luò)IO讀取數(shù)據(jù)熬苍。
RUNNABLE狀態(tài)也可以理解為存活著正在嘗試征用CPU的線程(有可能這個(gè)瞬間并沒(méi)有占用CPU,但是它可能正在發(fā)送指令等待系統(tǒng)調(diào)度)袁翁。由于在真正的系統(tǒng)中柴底,并不是開(kāi)啟一個(gè)線程后,CPU就只為這一個(gè)線程服務(wù)粱胜,它必須使用許多調(diào)度算法來(lái)達(dá)到某種平衡柄驻,不過(guò)這個(gè)時(shí)候線程依然處于RUNNABLE狀態(tài)。
BLOCKED
BLOCKED稱為阻塞狀態(tài)焙压,或者說(shuō)線程已經(jīng)被掛起鸿脓,它“睡著”了,原因通常是它在等待一個(gè)“鎖”涯曲,當(dāng)嘗試進(jìn)入一個(gè)synchronized語(yǔ)句塊/方法時(shí)野哭,鎖已經(jīng)被其它線程占有,就會(huì)被阻塞幻件,直到另一個(gè)線程走完臨界區(qū)或發(fā)生了相應(yīng)鎖對(duì)象的wait()操作后拨黔,它才有機(jī)會(huì)去爭(zhēng)奪進(jìn)入臨界區(qū)的權(quán)利
在Java代碼中,需要考慮synchronized的粒度問(wèn)題绰沥,否則一個(gè)線程長(zhǎng)時(shí)間占用鎖篱蝇,其它爭(zhēng)搶鎖的線程會(huì)一直阻塞,直到擁有鎖的線程釋放鎖
處于BLOCKED狀態(tài)的線程徽曲,即使對(duì)其調(diào)用 thread.interrupt()也無(wú)法改變其阻塞狀態(tài)零截,因?yàn)閕nterrupt()方法只是設(shè)置線程的中斷狀態(tài),即做一個(gè)標(biāo)記秃臣,不能喚醒處于阻塞狀態(tài)的線程
注意:ReentrantLock.lock()操作后進(jìn)入的是WAITING狀態(tài)涧衙,其內(nèi)部調(diào)用的是LockSupport.park()方法
WAITING
處于這種狀態(tài)的線程不會(huì)被分配CPU執(zhí)行時(shí)間,它們要等待顯示的被其它線程喚醒。這種狀態(tài)通常是指一個(gè)線程擁有對(duì)象鎖后進(jìn)入到相應(yīng)的代碼區(qū)域后绍撞,調(diào)用相應(yīng)的“鎖對(duì)象”的wait()方法操作后產(chǎn)生的一種結(jié)果。變相的實(shí)現(xiàn)還有LockSupport.park()得院、Thread.join()等傻铣,它們也是在等待另一個(gè)事件的發(fā)生,也就是描述了等待的意思祥绞。
以下方法會(huì)讓線程陷入無(wú)限期等待狀態(tài):
(1)沒(méi)有設(shè)置timeout參數(shù)的Object.wait()
(2)沒(méi)有設(shè)置timeout參數(shù)的Thread.join()
(3)LockSupport.park()
注意:
LockSupport.park(Object blocker) 會(huì)掛起當(dāng)前線程非洲,參數(shù)blocker是用于設(shè)置當(dāng)前線程的“volatile Object parkBlocker 成員變量”
parkBlocker 是用于記錄線程是被誰(shuí)阻塞的,可以通過(guò)LockSupport.getBlocker()獲取到阻塞的對(duì)象蜕径,用于監(jiān)控和分析線程用的两踏。
“阻塞”與“等待”的區(qū)別:
(1)“阻塞”狀態(tài)是等待著獲取到一個(gè)排他鎖,進(jìn)入“阻塞”狀態(tài)都是被動(dòng)的兜喻,離開(kāi)“阻塞”狀態(tài)是因?yàn)槠渌€程釋放了鎖梦染,不阻塞了;
(2)“等待”狀態(tài)是在等待一段時(shí)間 或者 喚醒動(dòng)作的發(fā)生朴皆,進(jìn)入“等待”狀態(tài)是主動(dòng)的
如主動(dòng)調(diào)用Object.wait()帕识,如無(wú)法獲取到ReentraantLock,主動(dòng)調(diào)用LockSupport.park()遂铡,如主線程主動(dòng)調(diào)用 subThread.join()肮疗,讓主線程等待子線程執(zhí)行完畢再執(zhí)行
離開(kāi)“等待”狀態(tài)是因?yàn)槠渌€程發(fā)生了喚醒動(dòng)作或者到達(dá)了等待時(shí)間
TIMED_WAITING
處于這種狀態(tài)的線程也不會(huì)被分配CPU執(zhí)行時(shí)間,不過(guò)無(wú)需等待被其它線程顯示的喚醒扒接,在一定時(shí)間之后它們會(huì)由系統(tǒng)自動(dòng)的喚醒伪货。
以下方法會(huì)讓線程進(jìn)入TIMED_WAITING限期等待狀態(tài):
(1)Thread.sleep()方法
(2)設(shè)置了timeout參數(shù)的Object.wait()方法
(3)設(shè)置了timeout參數(shù)的Thread.join()方法
(4)LockSupport.parkNanos()方法
(5)LockSupport.parkUntil()方法
TERMINATED
已終止線程的線程狀態(tài),線程已經(jīng)結(jié)束執(zhí)行钾怔。換句話說(shuō)碱呼,run()方法走完了,線程就處于這種狀態(tài)蒂教。其實(shí)這只是Java語(yǔ)言級(jí)別的一種狀態(tài)巍举,在操作系統(tǒng)內(nèi)部可能已經(jīng)注銷了相應(yīng)的線程,或者將它復(fù)用給其他需要使用線程的請(qǐng)求凝垛,而在Java語(yǔ)言級(jí)別只是通過(guò)Java代碼看到的線程狀態(tài)而已懊悯。
四. Thread類中的方法
通過(guò)查看java.lang.Thread類的源碼可知:
Thread類實(shí)現(xiàn)了Runnable接口,在Thread類中梦皮,有一些比較關(guān)鍵的屬性炭分,比如name是表示Thread的名字,可以通過(guò)Thread類的構(gòu)造器中的參數(shù)來(lái)指定線程名字剑肯,priority表示線程的優(yōu)先級(jí)(最大值為10捧毛,最小值為1,默認(rèn)值為5),daemon表示線程是否是守護(hù)線程呀忧,target表示要執(zhí)行的任務(wù)师痕。
下面是Thread類中常用的方法:
以下是關(guān)系到線程運(yùn)行狀態(tài)的幾個(gè)方法:
1)start方法
start()用來(lái)啟動(dòng)一個(gè)線程,當(dāng)調(diào)用start方法后而账,系統(tǒng)才會(huì)開(kāi)啟一個(gè)新的線程來(lái)執(zhí)行用戶定義的子任務(wù)胰坟,在這個(gè)過(guò)程中,會(huì)為相應(yīng)的線程分配需要的資源泞辐。
2)run方法
run()方法是不需要用戶來(lái)調(diào)用的笔横,當(dāng)通過(guò)start方法啟動(dòng)一個(gè)線程之后,當(dāng)線程獲得了CPU執(zhí)行時(shí)間咐吼,便進(jìn)入run方法體去執(zhí)行具體的任務(wù)吹缔。注意,繼承Thread類必須重寫run方法锯茄,在run方法中定義具體要執(zhí)行的任務(wù)厢塘。
3)sleep方法
sleep方法有兩個(gè)重載版本:
//參數(shù)為毫秒
sleep(long millis)
//第一參數(shù)為毫秒,第二個(gè)參數(shù)為納秒
sleep(long millis,int nanoseconds)
sleep相當(dāng)于讓線程睡眠肌幽,交出CPU俗冻,讓CPU去執(zhí)行其他的任務(wù)。
但是有一點(diǎn)要非常注意牍颈,sleep方法不會(huì)釋放鎖迄薄,也就是說(shuō)如果當(dāng)前線程持有對(duì)某個(gè)對(duì)象的鎖,則即使調(diào)用sleep方法煮岁,其他線程也無(wú)法訪問(wèn)這個(gè)對(duì)象讥蔽。看下面這個(gè)例子就清楚了:
public class Test {
private int i = 10;
private Object object = new Object();
public static void main(String[] args) throws IOException {
Test test = new Test();
MyThread thread1 = test.new MyThread();
MyThread thread2 = test.new MyThread();
thread1.start();
thread2.start();
}
class MyThread extends Thread{
@Override
public void run() {
synchronized (object) {
i++;
System.out.println("i:"+i);
try {
System.out.println("線程"+Thread.currentThread().getName()+"進(jìn)入睡眠狀態(tài)");
Thread.currentThread().sleep(10000);
} catch (InterruptedException e) {
// TODO: handle exception
}
System.out.println("線程"+Thread.currentThread().getName()+"睡眠結(jié)束");
i++;
System.out.println("i:"+i);
}
}
}
}
輸出結(jié)果:
從上面輸出結(jié)果可以看出画机,當(dāng)Thread-0進(jìn)入睡眠狀態(tài)之后冶伞,Thread-1并沒(méi)有去執(zhí)行具體的任務(wù)。只有當(dāng)Thread-0執(zhí)行完之后步氏,此時(shí)Thread-0釋放了對(duì)象鎖响禽,Thread-1才開(kāi)始執(zhí)行。
注意荚醒,如果調(diào)用了sleep方法芋类,必須捕獲InterruptedException異常或者將該異常向上層拋出界阁。當(dāng)線程睡眠時(shí)間滿后侯繁,不一定會(huì)立即得到執(zhí)行,因?yàn)榇藭r(shí)可能CPU正在執(zhí)行其他的任務(wù)泡躯。所以說(shuō)調(diào)用sleep方法相當(dāng)于讓線程進(jìn)入阻塞狀態(tài)贮竟。
4)yield方法
調(diào)用yield方法會(huì)讓當(dāng)前線程交出CPU權(quán)限丽焊,讓CPU去執(zhí)行其他的線程。它跟sleep方法類似咕别,同樣不會(huì)釋放鎖技健。但是yield不能控制具體的交出CPU的時(shí)間,另外惰拱,yield方法只能讓擁有相同優(yōu)先級(jí)的線程有獲取CPU執(zhí)行時(shí)間的機(jī)會(huì)凫乖。
注意,調(diào)用yield方法并不會(huì)讓線程進(jìn)入阻塞狀態(tài)弓颈,而是讓線程重回就緒狀態(tài),它只需要等待重新獲取CPU執(zhí)行時(shí)間删掀,這一點(diǎn)是和sleep方法不一樣的翔冀。
5)join方法
join方法有三個(gè)重載版本:
join()
join(long millis) //參數(shù)為毫秒
join(long millis,int nanoseconds) //第一參數(shù)為毫秒,第二個(gè)參數(shù)為納秒
假如在main線程中披泪,調(diào)用thread.join方法纤子,則main方法會(huì)等待thread線程執(zhí)行完畢或者等待一定的時(shí)間。如果調(diào)用的是無(wú)參join方法款票,則等待thread執(zhí)行完畢控硼,如果調(diào)用的是指定了時(shí)間參數(shù)的join方法,則等待一定的時(shí)間艾少。
看下面一個(gè)例子:
public class Test {
public static void main(String[] args) throws IOException {
System.out.println("進(jìn)入線程"+Thread.currentThread().getName());
Test test = new Test();
MyThread thread1 = test.new MyThread();
thread1.start();
try {
System.out.println("線程"+Thread.currentThread().getName()+"等待");
thread1.join();
System.out.println("線程"+Thread.currentThread().getName()+"繼續(xù)執(zhí)行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
class MyThread extends Thread{
@Override
public void run() {
System.out.println("進(jìn)入線程"+Thread.currentThread().getName());
try {
Thread.currentThread().sleep(5000);
} catch (InterruptedException e) {
}
System.out.println("線程"+Thread.currentThread().getName()+"執(zhí)行完畢");
}
}
}
輸出結(jié)果:
可以看出卡乾,當(dāng)調(diào)用thread1.join()方法后,main線程會(huì)進(jìn)入等待缚够,然后等待thread1執(zhí)行完之后再繼續(xù)執(zhí)行幔妨。
實(shí)際上調(diào)用join方法是調(diào)用了Object的wait方法,這個(gè)可以通過(guò)查看源碼得知:
wait方法會(huì)讓線程進(jìn)入阻塞狀態(tài)谍椅,并且會(huì)釋放線程占有的鎖误堡,并交出CPU執(zhí)行權(quán)限。
由于wait方法會(huì)讓線程釋放對(duì)象鎖雏吭,所以join方法同樣會(huì)讓線程釋放對(duì)一個(gè)對(duì)象持有的鎖锁施。
6)interrupt方法
interrupt,顧名思義杖们,即中斷的意思悉抵。
單獨(dú)調(diào)用interrupt方法可以使得處于阻塞狀態(tài)的線程拋出一個(gè)異常,也就說(shuō)摘完,它可以用來(lái)中斷一個(gè)正處于阻塞狀態(tài)的線程基跑;另外,通過(guò)interrupt方法和isInterrupted()方法來(lái)停止正在運(yùn)行的線程描焰。
下面看一個(gè)例子:
public class Test {
public static void main(String[] args) throws IOException {
Test test = new Test();
MyThread thread = test.new MyThread();
thread.start();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
}
thread.interrupt();
}
class MyThread extends Thread{
@Override
public void run() {
try {
System.out.println("進(jìn)入睡眠狀態(tài)");
Thread.currentThread().sleep(10000);
System.out.println("睡眠完畢");
} catch (InterruptedException e) {
System.out.println("得到中斷異常");
}
System.out.println("run方法執(zhí)行完畢");
}
}
}
輸出結(jié)果:
從這里可以看出媳否,通過(guò)interrupt方法可以中斷處于阻塞狀態(tài)的線程栅螟。那么能不能中斷處于非阻塞狀態(tài)的線程呢?看下面這個(gè)例子:
public class Test {
public static void main(String[] args) throws IOException {
Test test = new Test();
MyThread thread = test.new MyThread();
thread.start();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
}
thread.interrupt();
}
class MyThread extends Thread{
@Override
public void run() {
int i = 0;
while(i<Integer.MAX_VALUE){
System.out.println(i+" while循環(huán)");
i++;
}
}
}
}
運(yùn)行該程序會(huì)發(fā)現(xiàn)篱竭,while循環(huán)會(huì)一直運(yùn)行直到變量i的值超出Integer.MAX_VALUE力图。所以說(shuō)直接調(diào)用interrupt方法不能中斷正在運(yùn)行中的線程。
但是如果配合isInterrupted()能夠中斷正在運(yùn)行的線程掺逼,因?yàn)檎{(diào)用interrupt方法相當(dāng)于將中斷標(biāo)志位置為true吃媒,那么可以通過(guò)調(diào)用isInterrupted()判斷中斷標(biāo)志是否被置位來(lái)中斷線程的執(zhí)行。比如下面這段代碼:
public class Test {
public static void main(String[] args) throws IOException {
Test test = new Test();
MyThread thread = test.new MyThread();
thread.start();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
}
thread.interrupt();
}
class MyThread extends Thread{
@Override
public void run() {
int i = 0;
while(!isInterrupted() && i<Integer.MAX_VALUE){
System.out.println(i+" while循環(huán)");
i++;
}
}
}
}
運(yùn)行會(huì)發(fā)現(xiàn)吕喘,打印若干個(gè)值之后赘那,while循環(huán)就停止打印了。
但是一般情況下不建議通過(guò)這種方式來(lái)中斷線程氯质,一般會(huì)在MyThread類中增加一個(gè)屬性 isStop來(lái)標(biāo)志是否結(jié)束while循環(huán)败玉,然后再在while循環(huán)中判斷isStop的值奋单。
class MyThread extends Thread{
private volatile boolean isStop = false;
@Override
public void run() {
int i = 0;
while(!isStop){
i++;
}
}
public void setStop(boolean stop){
this.isStop = stop;
}
}
那么就可以在外面通過(guò)調(diào)用setStop方法來(lái)終止while循環(huán)砂轻。
7)stop方法
stop方法已經(jīng)是一個(gè)廢棄的方法守伸,它是一個(gè)不安全的方法。因?yàn)檎{(diào)用stop方法會(huì)直接終止run方法的調(diào)用辕漂,并且會(huì)拋出一個(gè)ThreadDeath錯(cuò)誤呢灶,如果線程持有某個(gè)對(duì)象鎖的話,會(huì)完全釋放鎖钉嘹,導(dǎo)致對(duì)象狀態(tài)不一致鸯乃。所以stop方法基本是不會(huì)被用到的。
8)destroy方法
destroy方法也是廢棄的方法跋涣§穑基本不會(huì)被使用到。
9)其他方法
以下是關(guān)系到線程屬性的幾個(gè)方法:
1)getId:用來(lái)得到線程ID
2)getName和setName:用來(lái)得到或者設(shè)置線程名稱仆潮。
3)getPriority和setPriority:用來(lái)獲取和設(shè)置線程優(yōu)先級(jí)宏蛉。
4)setDaemon和isDaemon:用來(lái)設(shè)置線程是否成為守護(hù)線程和判斷線程是否是守護(hù)線程。守護(hù)線程和用戶線程的區(qū)別在于:守護(hù)線程依賴于創(chuàng)建它的線程性置,而用戶線程則不依賴拾并。舉個(gè)簡(jiǎn)單的例子:如果在main線程中創(chuàng)建了一個(gè)守護(hù)線程,當(dāng)main方法運(yùn)行完畢之后鹏浅,守護(hù)線程也會(huì)隨著消亡嗅义。而用戶線程則不會(huì),用戶線程會(huì)一直運(yùn)行直到其運(yùn)行完畢隐砸。在JVM中之碗,像垃圾收集器線程就是守護(hù)線程。
5)currentThread:Thread類有一個(gè)比較常用的靜態(tài)方法currentThread()用來(lái)獲取當(dāng)前線程季希。
在上面已經(jīng)說(shuō)到了Thread類中的大部分方法褪那,那么Thread類中的方法調(diào)用到底會(huì)引起線程狀態(tài)發(fā)生怎樣的變化呢幽纷?下面一幅圖就是在上面的圖上進(jìn)行改進(jìn)而來(lái)的: