本文內容
Java進行線程調度的方法.
如何讓線程進入等待.
wait set概念的理解.
如何激活等待狀態的線程.
進行線程協調的方法
從上一章《線程的互斥》中我們知道,當方法體或代碼塊被synchronized方法修飾時,有一個線程在執行這部分代碼時,其它線程無法同時執行這部分代碼。但如果我們想進行更高效的處理如主動調整線程而不是讓線程被動等待和盲目競爭時該怎么處理呢?
在Java中,有三個方法可以對線程進行主動高效的控制,它們是wait,notify和notifyAll。
wait是主動讓線程等待的方法,而notify和notifyAll則是激活等待中的線程的方法。他們都是Object類的方法,也就是說任何對象都可以使用這三個方法。
Wait Set-線程的休息室
在學習使用wait,notify和notifyAll這三個方法之前,我們可以先理解一下Wait Set的概念,它是一個在某實例執行wait方法時,停止操作的線程的集合,類似于線程的休息室,每個實例都擁有這樣一個休息室。
Wait方法是用來把線程請入這個休息室的,而notify和notifyAll這兩個方法是用來將進入休息室休息的線程激活的。
wait Set是一個虛擬的概念,它既不是實例的字段,也不是可以獲取在實例上wait中線程的列表的方法.它只是用來幫助我們理解線程的等待和激活的。
Wait方法,將線程放入Wait Set
使用Wait方法時,線程即進入Wait set。如線程在執行代碼時遇到這樣的語句:xxObj.wait();則目前的線程會暫時停止運行,進入實例xxObj的wait Set.
當線程進入Wait Set時,即釋放了對該實例的鎖定.也就是說,即使是被synchronized修飾的方法和代碼塊,當第一個線程進入實例的wait Set等待后,其它線程就可以再進入這部分代碼了.
wait()前如果不寫某對象表示其前面的對象是this, wait()=this.wait();
notify方法-從wait set中激活一個線程
使用notify方法時,程序會從處于等待的實例的休息室中激活一個線程.代碼如下:
xxObj.notify();程序將從xxObj的wait set中挑出一個激活.這個線程即準備退出wait set.當當前線程釋放對xxObj的鎖定后,這個線程即獲取這個鎖定,從上次的停止點-執行wait的地方開始運行。
線程必須有調用的實例的鎖定,才能執行notify方法.
Wait set中處于等待狀態的線程有多個,具體激活那一個依環境和系統而變,事先無法辯明.我們大致可理解為隨機挑選了一個.
notifyAll方法-激活wait set中所有等待的線程
當執行notifyAll方法時,實例中所有處于等待狀態的線程都會被激活.代碼為:
xxObj.notifyAll();執行此代碼后xxObj的wait set中所有處于等待中的線程都會被激活.但具體那個線程獲取執行wait方法時釋放的鎖定要靠競爭,最終只能有一個線程獲得鎖定,其它的線程只能繼續回去等待.
notifyAll與notify的差異在于notifyAll激活所有wait set中等待的線程,而notify只激活其中的一個.
該使用notify還是notifyAll
建議:
1) 選擇notifyAll比notify穩當安全,如果notify處理得不好,程序會有隱患.
2) 選擇notifyAll比notify可靠,是大多數程序的首選.
3) 當你對代碼已經很清楚,對線程理解也很透徹時,你可以選擇使用notify,發揮其處理速度高的優勢.
當前線程必須持有欲調用實例的鎖定,才能調用wait,notify和notifyAll這三個方法.
如果代碼是xxObj.notifyAll(或wait, notify)(),則這行代碼必須處于synchronized(xxObj){…}代碼塊中.
如果代碼是this.notifyAll(或wait, notify)(),則這行代碼必須處于synchronized修飾的方法中.
前面說過, notifyAll和notify會激活線程去獲得進入wait時釋放的鎖定,但這個鎖定要等剛才執行notifyAll或notify方法的線程釋放這個鎖定才能獲取.
總結
1) wait,notify和notifyAll都是java.lang.Object的方法,它們用來對線程進行調度.
2) obj.wait()是把執行這句的線程放入obj的wait set中.
3) obj.notify()是從wait set中喚醒一個線程,這個線程在當前線程釋放對obj的鎖定后即獲取這個鎖定.
4) obj.notifyAll()是喚醒所有在obj的wait set中的線程,這批線程在當前線程釋放obj的鎖定后去競爭這個鎖定,最終只有一個能獲得,其它的又重新返回wait set等待下一次激活.
5) 執行這個wait,notify和notifyAll這三個方法前,當前線程(即執行obj.wait(), obj.notify()和obj.notifyAll()代碼的線程)必須持有obj的鎖定.