在 Java 中線程的生命周期中一共有 6 種狀態(tài)。New(新創(chuàng)建);Runnable(可運行);Blocked(被阻塞);Waiting(等待);Timed Waiting(計時等待);Terminated(被終止)。如果想要確定線程當(dāng)前的狀態(tài),可以通過 getState() 方法,并且線程在任何時刻只可能處于 1 種狀態(tài)。
New 新創(chuàng)建
New 表示線程被創(chuàng)建但尚未啟動的狀態(tài):當(dāng)我們用 new Thread() 新建一個線程時,如果線程沒有開始運行 start() 方法,所以也沒有開始執(zhí)行 run() 方法里面的代碼,那么此時它的狀態(tài)就是 New。而一旦線程調(diào)用了 start(),它的狀態(tài)就會從 New 變成 Runnable,也就是狀態(tài)轉(zhuǎn)換圖中中間的這個大方框里的內(nèi)容。
Runnable 可運行
Java 中的 Runable 狀態(tài)對應(yīng)操作系統(tǒng)線程狀態(tài)中的兩種狀態(tài),分別是 Running 和 Ready,也就是說,Java 中處于 Runnable 狀態(tài)的線程有可能正在執(zhí)行,也有可能沒有正在執(zhí)行,正在等待被分配 CPU 資源。所以,如果一個正在運行的線程是 Runnable 狀態(tài),當(dāng)它運行到任務(wù)的一半時,執(zhí)行該線程的 CPU 被調(diào)度去做其他事情,導(dǎo)致該線程暫時不運行,它的狀態(tài)依然不變,還是 Runnable,因為它有可能隨時被調(diào)度回來繼續(xù)執(zhí)行任務(wù)。
接下來,我們來看下 Runnable 下面的三個方框,它們統(tǒng)稱為阻塞狀態(tài),在 Java 中阻塞狀態(tài)通常不僅僅是 Blocked,實際上它包括三種狀態(tài),分別是 Blocked(被阻塞)、Waiting(等待)、Timed Waiting(計時等待),這三 種狀態(tài)統(tǒng)稱為阻塞狀態(tài),下面我們來看看這三種狀態(tài)具體是什么含義。
Blocked 被阻塞
首先來看最簡單的 Blocked,從箭頭的流轉(zhuǎn)方向可以看出,從 Runnable 狀態(tài)進(jìn)入 Blocked 狀態(tài)只有一種可能,就是進(jìn)入 synchronized 保護(hù)的代碼時沒有搶到 monitor 鎖,無論是進(jìn)入 synchronized 代碼塊,還是 synchronized 方法,都是一樣。我們再往右看,當(dāng)處于 Blocked 的線程搶到 monitor 鎖,就會從 Blocked 狀態(tài)回到Runnable 狀態(tài)。
Waiting 等待
我們再看看 Waiting 狀態(tài),線程進(jìn)入 Waiting 狀態(tài)有三種可能性。
沒有設(shè)置 Timeout 參數(shù)的 Object.wait() 方法。
沒有設(shè)置 Timeout 參數(shù)的 Thread.join() 方法。
LockSupport.park() 方法。
Blocked 僅僅針對 synchronized monitor 鎖,可是在 Java 中還有很多其他的鎖,比如 ReentrantLock,如果線程在獲取這種鎖時沒有搶到該鎖就會進(jìn)入 Waiting 狀態(tài),因為本質(zhì)上它執(zhí)行了 LockSupport.park() 方法,所以會進(jìn)入 Waiting 狀態(tài)。同樣,Object.wait() 和 Thread.join() 也會讓線程進(jìn)入 Waiting 狀態(tài)。Blocked 與 Waiting 的區(qū)別是 Blocked 在等待其他線程釋放 monitor 鎖,而 Waiting 則是在等待某個條件,比如 join 的線程執(zhí)行完畢,或者是 notify()/notifyAll() 。
Timed Waiting 限期等待
在 Waiting 上面是 Timed Waiting 狀態(tài),這兩個狀態(tài)是非常相似的,區(qū)別僅在于有沒有時間限制,Timed Waiting 會等待超時,由系統(tǒng)自動喚醒,或者在超時前被喚醒信號喚醒。
以下情況會讓線程進(jìn)入 Timed Waiting 狀態(tài)。
設(shè)置了時間參數(shù)的 Thread.sleep(long millis) 方法;
設(shè)置了時間參數(shù)的 Object.wait(long timeout) 方法;
設(shè)置了時間參數(shù)的 Thread.join(long millis) 方法;
設(shè)置了時間參數(shù)的 LockSupport.parkNanos(long nanos) 方法和 LockSupport.parkUntil(long deadline) 方法。
我們再來看下如何從這三種狀態(tài)流轉(zhuǎn)到下一個狀態(tài)。
想要從 Blocked 狀態(tài)進(jìn)入 Runnable 狀態(tài),要求線程獲取 monitor 鎖,而從 Waiting 狀態(tài)流轉(zhuǎn)到其他狀態(tài)則比較特殊,因為首先 Waiting 是不限時的,也就是說無論過了多長時間它都不會主動恢復(fù)。
只有當(dāng)執(zhí)行了 LockSupport.unpark(),或者 join 的線程運行結(jié)束,或者被中斷時才可以進(jìn)入 Runnable 狀態(tài)。
如果其他線程調(diào)用 notify() 或 notifyAll()來喚醒它,它會直接進(jìn)入 Blocked 狀態(tài),這是為什么呢?因為喚醒 Waiting 線程的線程如果調(diào)用 notify() 或 notifyAll(),要求必須首先持有該 monitor 鎖,所以處于 Waiting 狀態(tài)的線程被喚醒時拿不到該鎖,就會進(jìn)入 Blocked 狀態(tài),直到執(zhí)行了 notify()/notifyAll() 的喚醒它的線程執(zhí)行完畢并釋放 monitor 鎖,才可能輪到它去搶奪這把鎖,如果它能搶到,就會從 Blocked 狀態(tài)回到 Runnable 狀態(tài)。
同樣在 Timed Waiting 中執(zhí)行 notify() 和 notifyAll() 也是一樣的道理,它們會先進(jìn)入 Blocked 狀態(tài),然后搶奪鎖成功后,再回到 Runnable 狀態(tài)。
當(dāng)然對于 Timed Waiting 而言,如果它的超時時間到了且能直接獲取到鎖/join的線程運行結(jié)束/被中斷/調(diào)用了LockSupport.unpark(),會直接恢復(fù)到 Runnable 狀態(tài),而無需經(jīng)歷 Blocked 狀態(tài)。
Terminated 終止
再來看看最后一種狀態(tài),Terminated 終止?fàn)顟B(tài),要想進(jìn)入這個狀態(tài)有兩種可能。run() 方法執(zhí)行完畢,線程正常退出。出現(xiàn)一個沒有捕獲的異常,終止了 run() 方法,最終導(dǎo)致意外終止。
注意點
最后我們再看線程轉(zhuǎn)換的兩個注意點。線程的狀態(tài)是需要按照箭頭方向來走的,比如線程從 New 狀態(tài)是不可以直接進(jìn)入 Blocked 狀態(tài)的,它需要先經(jīng)歷 Runnable 狀態(tài)。線程生命周期不可逆:一旦進(jìn)入 Runnable 狀態(tài)就不能回到 New 狀態(tài);一旦被終止就不可能再有任何狀態(tài)的變化。所以一個線程只能有一次 New 和 Terminated 狀態(tài),只有處于中間狀態(tài)才可以相互轉(zhuǎn)換。
-
JAVA
+關(guān)注
關(guān)注
19文章
2966瀏覽量
104704 -
JAVA語言
+關(guān)注
關(guān)注
0文章
138瀏覽量
20090
發(fā)布評論請先 登錄
相關(guān)推薦
評論