New 表示線程被創(chuàng)建但尚未啟動(dòng)的狀態(tài):當(dāng)我們用 new Thread() 新建一個(gè)線程時(shí)榛了,如果線程沒(méi)有開始運(yùn)行 start() 方法玉锌,所以也沒(méi)有開始執(zhí)行 run() 方法里面的代碼,那么此時(shí)它的狀態(tài)就是 New瓮顽。而一旦線程調(diào)用了 start()县好,它的狀態(tài)就會(huì)從 New 變成 Runnable。
Java 中的 Runable 狀態(tài)對(duì)應(yīng)操作系統(tǒng)線程狀態(tài)中的兩種狀態(tài)暖混,分別是 Running 和 Ready缕贡,也就是說(shuō),Java 中處于 Runnable 狀態(tài)的線程有可能正在執(zhí)行,也有可能沒(méi)有正在執(zhí)行晾咪,正在等待被分配 CPU 資源收擦。
所以,如果一個(gè)正在運(yùn)行的線程是 Runnable 狀態(tài)谍倦,當(dāng)它運(yùn)行到任務(wù)的一半時(shí)塞赂,執(zhí)行該線程的 CPU 被調(diào)度去做其他事情,導(dǎo)致該線程暫時(shí)不運(yùn)行剂跟,它的狀態(tài)依然不變减途,還是 Runnable,因?yàn)樗锌赡茈S時(shí)被調(diào)度回來(lái)繼續(xù)執(zhí)行任務(wù)曹洽。
在 Java 中阻塞狀態(tài)通常不僅僅是 Blocked鳍置,實(shí)際上它包括三種狀態(tài),分別是 Blocked(被阻塞)送淆、Waiting(等待)税产、Timed Waiting(計(jì)時(shí)等待),這三 種狀態(tài)統(tǒng)稱為阻塞狀態(tài)偷崩,下面我們來(lái)看看這三種狀態(tài)具體是什么含義辟拷。
首先來(lái)看最簡(jiǎn)單的 Blocked,從箭頭的流轉(zhuǎn)方向可以看出阐斜,從 Runnable 狀態(tài)進(jìn)入 Blocked 狀態(tài)只有一種可能衫冻,就是進(jìn)入 synchronized 保護(hù)的代碼時(shí)沒(méi)有搶到 monitor 鎖,無(wú)論是 進(jìn)入 synchronized 代碼塊谒出,還是 synchronized 方法隅俘,都是一樣。
當(dāng)處于 Blocked 的線程搶到 monitor 鎖笤喳,就會(huì)從 Blocked 狀態(tài)回到 Runnable 狀態(tài)为居。
線程進(jìn)入 Waiting 狀態(tài)有三種可能性:
沒(méi)有設(shè)置 Timeout 參數(shù)的 Object.wait() 方法。
沒(méi)有設(shè)置 Timeout 參數(shù)的 Thread.join() 方法杀狡。
LockSupport.park() 方法蒙畴。
Blocked 僅僅針對(duì) synchronized monitor 鎖,可是在 Java 中還有很多其他的鎖呜象,比如 ReentrantLock膳凝,如果線程在獲取這種鎖時(shí)沒(méi)有搶到該鎖就會(huì)進(jìn)入 Waiting 狀態(tài),因?yàn)楸举|(zhì)上它執(zhí)行了 LockSupport.park() 方法恭陡,所以會(huì)進(jìn)入 Waiting 狀態(tài)鸠项。同樣,Object.wait() 和 Thread.join() 也會(huì)讓線程進(jìn)入 Waiting 狀態(tài)子姜。
Blocked 與 Waiting 的區(qū)別是 Blocked 在 等待其他線程釋放 monitor 鎖,而 Waiting 則是在等待某個(gè)條件,比如 join 的線程執(zhí)行完畢哥捕,或者是 notify()/notifyAll() 牧抽。
在 Waiting 上面是 Timed Waiting 狀態(tài),這兩個(gè)狀態(tài)是非常相似的遥赚,區(qū)別僅在于有沒(méi)有時(shí)間限制扬舒,Timed Waiting 如果等待超時(shí),由系統(tǒng)自動(dòng)喚醒凫佛,或者在超時(shí)前被喚醒信號(hào)喚醒讲坎。
以下情況會(huì)讓線程進(jìn)入 Timed Waiting 狀態(tài):
1,設(shè)置了時(shí)間參數(shù)的 Thread.sleep(long millis)
方法愧薛;
2晨炕,設(shè)置了時(shí)間參數(shù)的 Object.wait(long timeout)
方法;
3毫炉,設(shè)置了時(shí)間參數(shù)的 Thread.join(long millis)
方法瓮栗;
4,設(shè)置了時(shí)間參數(shù)的 LockSupport.parkNanos(long nanos)
方法和 LockSupport.parkUntil(long deadline)
方法瞄勾。
我們?cè)賮?lái)看下如何在這三種狀態(tài)之間進(jìn)行一個(gè)切換:
想要從 Blocked 狀態(tài)進(jìn)入 Runnable 狀態(tài)费奸,要求線程獲取 monitor 鎖,而從 Waiting 狀態(tài)流轉(zhuǎn)到其他狀態(tài)則比較特殊进陡,因?yàn)槭紫?Waiting 是不限時(shí)的愿阐,也就是說(shuō)無(wú)論過(guò)了多長(zhǎng)時(shí)間它都不會(huì) 主動(dòng)恢復(fù),只有當(dāng)執(zhí)行了 LockSupport.unpark()
趾疚,或者 join 的線程運(yùn)行結(jié)束缨历,或者被中斷時(shí)才可以進(jìn)入 Runnable 狀態(tài)。
如果其他線程調(diào)用 notify() 或 notifyAll() 來(lái)喚醒它盗蟆,它會(huì)直接進(jìn)入 Blocked 狀態(tài)戈二,這是為什么呢?
喚醒 Waiting 線程的線程如果調(diào)用 notify() 或 notifyAll()喳资,要求它必須先持有 monitor 鎖觉吭,如果處于 Waiting 狀態(tài)的線程被喚醒時(shí)拿不到該鎖,** Waiting 狀態(tài)的線程** 就會(huì)進(jìn)入 Blocked 狀態(tài)仆邓,直到 喚醒它的線程 的 notify()/notifyAll() 執(zhí)行完畢并釋放 monitor 鎖鲜滩,才可能輪到它去搶奪這把鎖,如果它能搶到节值,就會(huì)從 Blocked 狀態(tài)回到 Runnable 狀態(tài)徙硅。
同樣在 Timed Waiting 中執(zhí)行 notify() 和 notifyAll() 也是一樣的道理,它們會(huì)先進(jìn)入 Blocked 狀態(tài)搞疗,然后搶奪鎖成功后嗓蘑,再回到 Runnable 狀態(tài)。
當(dāng)然對(duì)于 Timed Waiting 而言,如果它的超時(shí)時(shí)間到了且能直接獲取到鎖/join的線程運(yùn)行結(jié)束/被中斷/調(diào)用了LockSupport.unpark()桩皿,會(huì)直接恢復(fù)到 Runnable 狀態(tài)豌汇,而無(wú)需經(jīng)歷 Blocked 狀態(tài)。
再來(lái)看看最后一種狀態(tài)泄隔,Terminated 終止?fàn)顟B(tài)拒贱,要想進(jìn)入這個(gè)狀態(tài)有兩種可能:
run() 方法執(zhí)行完畢,線程正常退出佛嬉。
出現(xiàn)一個(gè)沒(méi)有捕獲的異常逻澳,終止了 run() 方法,最終導(dǎo)致意外終止暖呕。
最后我們?cè)倏淳€程狀態(tài)轉(zhuǎn)換的兩個(gè)注意點(diǎn):
線程從 New 狀態(tài)是不可以直接進(jìn)入 Blocked 狀態(tài)的斜做,它需要先經(jīng)歷 Runnable 狀態(tài)。
線程生命周期不可逆:一旦進(jìn)入 Runnable 狀態(tài)就不能回到 New 狀態(tài)缰揪;一旦被終止就不可能再有任何狀態(tài)的變化陨享。所以一個(gè)線程只能有一次 New 和 Terminated 狀態(tài),只有處于中間狀態(tài)才可以相互轉(zhuǎn)換钝腺。