天天看點

java基礎之線程等待喚醒機制(适合小白)

今天我們來聊聊線程中的等待喚醒機制。

java中線程有以下六種狀态:建立狀态,阻塞狀态,運作狀态,死亡狀态,休眠狀态以及無限等待狀态。

當我們手動new一個Thread或者其子類的時候,此時線程就處于建立狀态(NEW)。調用start方法,如果CPU此時處于空閑狀态(該線程搶奪到CPU執行時間),則該線程進入運作狀态(RUNNABLE),否則進入阻塞狀态(BLOCKED)準備搶奪下一次CPU執行時間。當該線程運作完畢或者調用stop方法結束該線程或者運作過程中抛出異常,則進入死亡狀态(TERMINATED)。隻有調用sleep(time)或者wait(time)方法後,線程才會進入休眠狀态(TIMED_WAITING)。當計時結束時,如果cpu空間,則該線程進入運作狀态,否則進入阻塞狀态。如果使用sleep()方法或者wait()方法(這裡兩個方法相比于前文少了時間參數),則該線程會進入永久等待狀态,直至調用notify方法喚醒。

休眠狀态/永久休眠狀态相比阻塞狀态的差別:

休眠狀态和永久休眠狀态都是主動放棄CPU資源,即使CPU處于空閑狀态,該線程仍然不會執行。

而阻塞狀态并沒有放棄CPU執行資格,一旦CPU空閑便會被執行。

java基礎之線程等待喚醒機制(适合小白)

鎖阻塞:新建立的線程處于可運作狀态(需要獲得CPU執行時間),如果未能争取到鎖對象,則進入鎖阻塞狀态,否則進入運作狀态。

接下來我們詳細了解下無限等待狀态。

先給出等待喚醒的案例場景以便于了解。

現在有個飯館賣飯的場景,飯店的廚師和來往的客人分别是生産者和消費者。來一個客人,點菜完畢後,需要等待廚師制作菜肴才能開吃。客人點菜後的等待可以看成無限等待狀态,直到廚師菜肴制作完畢後通知他,他才會被喚醒。

現在上代碼:

java基礎之線程等待喚醒機制(适合小白)

這裡建立了兩個匿名内部類分别模拟生産者和消費者。客人點菜後需要等待廚師制作,這裡我們設定了五秒的時間來模拟。需要注意的是,

obj.wait()

obj.notify()兩句代碼。

這裡不論是睡眠還是喚醒,他們都需要使用同一個鎖對象來執行!

運作結果:

java基礎之線程等待喚醒機制(适合小白)

等待五秒後...

java基礎之線程等待喚醒機制(适合小白)

這裡生産者(廚師)和消費者(客人)分别是兩個線程。

客人要吃飯(消費者需要資源),通知廚師做飯(喚醒生産者線程),廚師做飯,消費者進入等待狀态。等廚師做飯完畢,會告訴消費者飯已經做好了(喚醒消費者線程),消費者吃飯(消費者線程被喚醒),廚師等待(生産者線程進入等待狀态)。等消費者吃飯完畢,會告訴廚師我吃完了,還要繼續吃,消費者進入等待狀态,廚師線程被喚醒......

wait():線程不再活動,不再參與排程,進入到wait set中,是以此時此線程不會浪費cpu資源,也不會去競争cpu資源。這時候該線程狀态就是WAITING。隻有别的線程執行一個動作——通知(notify)在這個對象上等待的線程從wait set中釋放出來,重新進入排程隊列。

notify:選取所通知的對象的wait set中的一個線程釋放。規則是:等待時間最長的線程優先被釋放。

notifyAll():釋放所通知對象上的全部線程到排程隊列。

注意:哪怕隻通知了一個線程,被通知的線程也不會立刻恢複執行狀态,因為它當國中斷的地方是在同步塊内,此時它并沒有持有鎖。是以他會和其他線程一同競争cpu資源。隻有它競争成功才能恢複執行。

下面給個例子模拟下線程的等待喚醒場景。

現在有個餐館,有客人去吃飯。我們需要三個類:客人(消費者),廚師(生産者)以及菜(公共資源)

首先是菜類:

java基礎之線程等待喚醒機制(适合小白)

餐廳類:

java基礎之線程等待喚醒機制(适合小白)

消費者類:

java基礎之線程等待喚醒機制(适合小白)

這裡為了保證消費者和生産者的互斥性(消費者和生産者同一時刻隻能有一人占有foot資源),必須使用同步技術。這裡使用synchronize同步代碼塊。

預設資源最初時為無(flag = false),消費者占有資源時發現資源不存在,自身進入等待狀态,調用wait方法。此時生産者獲得資源,發現資源不存在,生産資源。當資源生産完畢,則通知消費者線程(notify)。消費者被喚醒後,消費資源,等資源消費完畢,喚醒生産者資源,如此反複循環。

main方法如下:

java基礎之線程等待喚醒機制(适合小白)

運作結果如下:

java基礎之線程等待喚醒機制(适合小白)