1. 程式人生 > >java線程狀態 以及 sheep()、wait()、yield() 區別

java線程狀態 以及 sheep()、wait()、yield() 區別

text 捕獲異常 線程阻塞 exce follow 邏輯 等於 join() 狀態

前言

最近看到很多人都在討論多線程的問題,於是寫出了這篇博客,希望可以幫到正在學習和使用這塊的朋友們,首先我們先看看兩個圖(兩個圖都來自其他碼農的分享)

技術分享圖片 技術分享圖片

這兩個圖是一樣的邏輯,這裏一起羅列出來,下面讓我們用語句來簡單描述下兩個圖:

sleep 讓線程從 【running】 -> 【阻塞態】 時間結束/interrupt -> 【runnable】
wait 讓線程從 【running】 -> 【等待隊列】notify -> 【鎖池】 -> 【runnable】

當我們看到這個圖的時候首先會看到線程的幾種狀態,下面讓我們先來分別說明一下:

線程共包括一下5種狀態:

1,新建、初始狀態(New) :線程對象被創建後就進入了新建狀態,Thread thread = new Thread();

2,就緒(Runnable):也被稱之為“可執行狀態”,當線程被new出來後,其他的線程調用了該對象的start()方法,即thread.start(),此時線程位於“可運行線程池”中,只等待獲取CPU的使用權,隨時可以被CPU調用。進入就緒狀態的進程除CPU之外,其他運行所需的資源都已經全部獲得。

3,運行(Running):線程獲取CPU權限開始執行。註意:線程只能從就緒狀態進入到運行狀態

4,阻塞(Bloacked):阻塞狀態是線程因為某種原因放棄CPU的使用權,暫時停止運行,知道線程進入就緒狀態後才能有機會轉到運行狀態。

阻塞的情況分三種:

(01) 等待阻塞:運行的線程執行wait()方法,該線程會釋放占用的所有資源,JVM會把該線程放入“等待池中”。進入這個狀態後是不能自動喚醒的,必須依靠其他線程調用notify()或者notifyAll()方法才能被喚醒。

(02)同步阻塞:運行的線程在獲取對象的(synchronized)同步鎖時,若該同步鎖被其他線程占用,則JVM會吧該線程放入“鎖池”中。

(03)其他阻塞:通過調用線程的sleep()或者join()或發出了I/O請求時,線程會進入到阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新回到就緒狀態

以上三種阻塞狀態請參考上面的2個圖示來理解

5,死亡(Dead):線程執行完了或因異常退出了run()方法,則該線程結束生命周期。

wait(), notify(), notifyAll()等方法介紹

這三個方法都是定義到Object類中,wait的作用是當當前線程釋放它所持有的鎖進入等待狀態,而notify和notifyAll則是喚醒當前對象上的等待線程。

notify() -- 喚醒在此對象監視器上等待的單個線程。
notifyAll() -- 喚醒在此對象監視器上等待的所有線程。

wait() -- 讓當前線程處於“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法”,當前線程被喚醒(進入“就緒狀態”)。
wait(long timeout) -- 讓當前線程處於“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者超過指定的時間量”,當前線程被喚醒(進入“就緒狀態”)。
wait(long timeout, int nanos) -- 讓當前線程處於“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者其他某個線程中斷當前線程,或者已超過某個實際時間量”,當前線程被喚醒(進入“就緒狀態”)。

wait()會使“當前線程”等待,並且會釋放到它所占用的“鎖標誌”,從而使線程所在對象中的其他synchronized數據可以被其他線程使用。

waite()和notify()因為會對對象的“鎖標誌”進行操作,所以它們必須在synchronized函數或synchronizedblock中進行調用。如果在non-synchronized函數或non-synchronizedblock中進行調用,雖然能編譯通過,但在運行時會發生IllegalMonitorStateException的異常。 負責喚醒等待線程的那個線程(我們稱為“喚醒線程”),它只有在獲取“該對象的同步鎖”(這裏的同步鎖必須和等待線程的同步鎖是同一個),並且調用notify()或notifyAll()方法之後,才能喚醒等待線程。雖然,等待線程被喚醒;但是,它不能立刻執行,因為喚醒線程還持有“該對象的同步鎖”。必須等到喚醒線程釋放了“對象的同步鎖”之後,等待線程才能獲取到“對象的同步鎖”進而繼續運行。

suspend()和 resume()方法

兩個方法配套使用,suspend()使得線程進入阻塞狀態,並且不會自動恢復,必須其對應的resume()被調用,才能使得線程重新進入可執行狀態。典型地,suspend()和 resume() 被用在等待另一個線程產生的結果的情形:測試發現結果還沒有產生後,讓線程阻塞,另一個線程產生了結果後,調用 resume()使其恢復。 註意區別: 初看起來wait() 和 notify() 方法與suspend()和 resume() 方法對沒有什麽分別,但是事實上它們是截然不同的。區別的核心在於,前面敘述的suspend()及其它所有方法在線程阻塞時都不會釋放占用的鎖(如果占用了的話),而wait() 和 notify() 這一對方法則相反。

sleep() 和 yield()方法

這兩個方法都定義在Thread.java中

sleep()的作用是讓當前線程休眠(正在執行的線程主動讓出cpu,然後cpu就可以去執行其他任務),即當前線程會從“運行狀態”進入到“休眠(阻塞)狀態”。sleep()會指定休眠時間,線程休眠的時候會大於或者等於該休眠時間,當時間過後該線程重新被會形式,他會由“阻塞狀態”編程“就緒狀態”,從而等待cpu的調度執行,註意:sleep方法只是讓出了cpu的執行權,並不會釋放同步資源鎖

yield()的作用是讓步,它能夠讓當前線程從“運行狀態”進入到“就緒狀態”,從而讓其他等待線程獲取執行權,但是不能保證在當前線程調用yield()之後,其他線程就一定能獲得執行權,也有可能是當前線程又回到“運行狀態”繼續運行,註意:這裏我將上面的“具有相同優先級”的線程直接改為了線程,很多資料都寫的是讓具有相同優先級的線程開始競爭,但其實不是這樣的,優先級低的線程在拿到cpu執行權後也是可以執行,只不過優先級高的線程拿到cpu執行權的概率比較大而已,並不是一定能拿到

舉個例子:一幫朋友在排隊上公交車,輪到Yield的時候,他突然說:我不想先上去了,咱們大家來競賽上公交車。然後所有人就一塊沖向公交車,

有可能是其他人先上車了,也有可能是Yield先上車了。

但是線程是有優先級的,優先級越高的人,就一定能第一個上車嗎?這是不一定的,優先級高的人僅僅只是第一個上車的概率大了一點而已,

最終第一個上車的,也有可能是優先級最低的人。並且所謂的優先級執行,是在大量執行次數中才能體現出來的。

wait和sleep的區別

相同點:

1,他們都是在多線程的環境下,都可以在程序的調用出阻塞指定的毫秒數並且返回

2,兩個方法都可以通過interrupt()方法打斷線程的暫停狀態,但是線程會拋出InterruptedException。需要註意的是,InterruptedException是線程自己從內部拋出的,並不是interrupt()方法拋出的。對某一線程調用 interrupt()時,如果該線程正在執行普通的代碼,那麽該線程根本就不會拋出InterruptedException。但是,一旦該線程進入到 wait()/sleep()/join()後,就會立刻拋出InterruptedException 。

不同點:

1,Thread類的方法:sleep(),yield()
Object的方法:wait()和notify()、notifyAll()

2,每個對象都有一個鎖來控制同步訪問。Synchronized關鍵字可以和對象的鎖交互,來實現線程的同步。 sleep方法沒有釋放鎖,而wait方法釋放了鎖,使得其他線程可以使用同步控制塊或者方法。

3, wait,notify和notifyAll只能在同步控制方法或者同步控制塊裏面使用,而sleep可以在任何地方使用 。註意:wiat()必須放在synchronized block中,否則會在program runtime時扔出“java.lang.IllegalMonitorStateException”異常。

4,sleep必須捕獲異常,而wait,notify和notifyAll不需要捕獲異常

綜上可得兩者最大的區別:sleep()睡眠時,保持對象鎖,仍然占有該鎖;而wait()睡眠時,釋放對象鎖。

註意:

第一:調用notify() 方法導致解除阻塞的線程是從因調用該對象的 wait()方法而阻塞的線程中隨機選取的,我們無法預料哪一個線程將會被選擇,所以編程時要特別小心,避免因這種不確定性而產生問題。

第二:除了notify(),還有一個方法 notifyAll()也可起到類似作用,唯一的區別在於,調用 notifyAll()方法將把因調用該對象的 wait()方法而阻塞的所有線程一次性全部解除阻塞。當然,只有獲得鎖的那一個線程才能進入可執行狀態。

談到阻塞,就不能不談一談死鎖,略一分析就能發現,suspend()方法和不指定超時期限的wait()方法的調用都可能產生死鎖。遺憾的是,Java並不在語言級別上支持死鎖的避免,我們在編程中必須小心地避免死鎖。

java線程狀態 以及 sheep()、wait()、yield() 區別