衆所周知,
Java併發系列編程
一直都是Java程序員難以輕易繞過的山,可謂之小高之山也。Java生態圈中提供了很是豐富的併發編程類庫,可是這樣子也造就了很是多的人知其然而不知其因此然,不少人只會用,殊不知其底層的運行機制,不知其優點與缺陷,也就沒法將其融會貫通,作到信手拈來。況且,即使很是完善的類庫也沒法知足全部的業務需求,適當的時候咱們可能要本身編寫類庫來支撐本身的業務。在這個系列中,咱們一塊兒來深刻學習併發編程,一塊兒成長。本人能力有限,如有錯誤,請及時指正。程序員
想要學習好多線程編程,無疑要先明白,線程在整個運行期間的狀態,又是經過何種機制,或者說何種方式讓線程在這些狀態之間切換?面試
如上圖,大體能夠說明Java線程的生命週期。編程
在多線程編程中,咱們一般會使用 Thread
, Runnable
關鍵字來建立線程,以下:多線程
Thread thread = new Thread(new Runnable(){
併發
@Override
ide
public void run() {
學習
System.out.println("thread");
spa
}
操作系統
});
.net
當這個線程被建立出來以後,若是你沒有調用 start()
(或者其餘方式好比當如線程池中調用 submit()
),它與普通對象並沒有區別(有面試官可能會在這裏考你,至少我經歷過)。此時OS調度器不會爲它分配CPU執行須要的時間分片。
當線程被建立出來以後,它必須進入就緒狀態,纔可能被分配CPU時間分片,才能夠可能被操做系統調度。一般狀況下,咱們會經過調用 start()
,或者線程池的 submit()
讓線程進入就緒狀態,以下:
thread.start(); // 普通線程
ExecutorService executor = Executors.newSingleThreadExecutor();// 建立線程池並將線程提交給線程池管理
executor.submit(thread);
線程進入就緒狀態大概有以下幾種方式:
線程重新建狀態進入就緒狀態,好比(start(),executor.submit())
線程從運行狀態恢復到就緒狀態,(好比正在執行的線程被掛起,CPU時間分片用完,yield())
線程從阻塞狀態恢復到就緒狀態(好比sleep執行完畢,join執行完畢)
在鎖池中等待的鎖拿到了鎖的權限
當線程處於就緒狀態時,隨時有可能被操做系統調度。線程從就緒狀態到被操做系統調度稱爲線程進入運行狀態,語義上是: run()
方法正在接受操做系統調度,正在被執行。
當線程從運行狀態調用了 sleep()
或者 join()
等方法的時候,會進入阻塞狀態,等待執行時間到,或者調用 join()
的線程執行完畢,線程會自動進入就緒狀態,以下:
try {
thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
當線程執行 run()
方法完畢,或者由於發生異常而被中斷,或者 interrupt()
(另,在jdk中依舊能夠看到一些已經被廢棄的諸如 stop(),supend()
等等),線程會進入死亡狀態。若是線程執行完 run()
方法以後,線程進入死亡狀態,並不意味該線程的對象被回收,線程也能夠再次被喚醒。
當正在運行線程調用 wait()
的時候,線程會丟失CPU時間分片,進入一個等待狀態,等待其餘線程調用 notify()/notifyAll()
的時候,將其喚醒。若是沒有被喚醒,線程一直處於等待狀態,也能夠稱之爲假死狀態。注意區別好線程調用 sleep()
進入睡眠狀態,兩者之間不盡相同,調用 sleep()
的語義是線程須要進入休眠狀態,休眠時間是在已經被設定好的,時間到了以後會自行進入就緒狀態等待操做系統調度。線程調用 wait()
的目的是擁有當前鎖對象的線程不須要繼續執行(釋放鎖,讓出CPU時間分片,且不會馬上進入就緒狀態),進入等待隊列的線程須要被 notify()/notifyAll()
喚醒。喚醒後,線程會進入鎖池中排隊去等待,直到拿到鎖的權限,只有拿到鎖的權限才能夠進入就緒狀態等待操做系統調度。
當咱們在調用 wait()
, notify()
, notifyAll()
方法的時候,編譯時能夠正常經過,可是運行期間可能會拋出異常 IllegalMonitorStateException
,這是爲什麼?在調用 wait()
語句的時候,前置條件是:當前線程必須擁有當前對象的鎖的權限。一般狀況下,咱們一般會說在調用 wait()
方法以前,必須先得到 synchronized
的對象鎖【這個語句自己不對,可是能夠很好說明問題】。
好比調用了線程的 join()
方法的時候,若是細心的同窗會去查看源碼,會發如今其內部是經過調用 wait(0)
方法來達到讓當前正在執行的線程進入等待狀態,源碼以下:
public final synchronized void join(long millis)
throws InterruptedException {
// ....
if (millis == 0) {
while (isAlive()) {
wait(0);// 能夠考慮wait()與wait(timeout)的區別
}
}
// ....
}
當正在執行的線程遇到 synchronized
時,或者在等待隊列中的線程被 notify()
, notifyAll()
喚醒的線程,會進入鎖池中,直到拿到鎖以後會進入就緒狀態繼續執行。 synchronized
關鍵字自沒必要多說,線程同步原語,由內置語言實現。當一個線程已經進入這個鎖,而且還未完成釋放,其餘的線程執行到這裏,想要繼續執行就須要進入鎖池中排隊等待當前的鎖釋放,直到獲取鎖(不必定是先到先得)才能夠進入就緒狀態。 前邊提到,調用 wait()
方法須要先得到鎖,它的語義更多的可能(猜想)是:當線程獲取鎖以後,在執行過程當中,因爲某些條件不知足而選擇主動進入等待狀態,直到其餘線程處理完一些事項後它纔會被喚醒並被操做系統從新調度。因此,只有在等待隊列中的線程才能夠調用 notify()
, notifyAll()
來喚醒。
以上爲筆者對於線程的生命週期的理解,如如有錯誤,請大俠指正!