Java 线程状态---WAITING(部分转载)
看到一篇關(guān)于寫線程waiting狀態(tài)的文章,感覺很生動有趣,轉(zhuǎn)過來保存下。
總結(jié):
waiting這個狀態(tài),就是等待,明確了等待,就不會搶資源了。
一個線程A在拿到鎖但不滿足執(zhí)行條件的時候,需要另一個線程B去滿足這個條件,
那么線程A就會釋放鎖并處于waiting的狀態(tài),等線程B執(zhí)行完再執(zhí)行。
waiting狀態(tài)的好處是:此狀態(tài)的線程不再活動,不再參與調(diào)度,因此不會浪費(fèi) CPU 資源,也不會去競爭鎖了,相比暴力的blocking狀態(tài),要優(yōu)雅很多。
進(jìn)入waiting的方法:
調(diào)用wait方法,就能讓線程進(jìn)入waiting狀態(tài),notify/notify能讓線程結(jié)束waiting狀態(tài)。
join也能讓線程進(jìn)入waiting狀態(tài),但也算是特殊的wait
還有個Time_waiting的狀態(tài)
就是wait方法加了個時間的入?yún)?#xff0c;代表等多久就不等了,避免永遠(yuǎn)等下去。
blocking和waiting
blocking狀態(tài),硬說也算waiting的特殊情況
如果waiting狀態(tài)的條件是有沒有鎖,那就可以勉強(qiáng)理解為blocking狀態(tài)吧。
blocking是線程被動阻塞,waiting是線程主動阻塞,本質(zhì)上其實(shí)是一樣的。
下文這個火車上搶廁所的例子,很形象。
以下是轉(zhuǎn)載正文(末尾附原文鏈接):
定義
一個正在無限期等待另一個線程執(zhí)行一個特別的動作的線程處于這一狀態(tài)。
A thread that is waiting indefinitely for another thread to perform a particular action is in this state.
然而這里并沒有詳細(xì)說明這個“特別的動作”到底是什么,詳細(xì)定義還是看 javadoc(jdk8):
一個線程進(jìn)入 WAITING 狀態(tài)是因?yàn)檎{(diào)用了以下方法:
- 不帶時限的 Object.wait 方法
- 不帶時限的 Thread.join 方法
- LockSupport.park
然后會等其它線程執(zhí)行一個特別的動作,比如:
- 一個調(diào)用了某個對象的 Object.wait 方法的線程會等待另一個線程調(diào)用此對象的 Object.notify() 或 Object.notifyAll()。
- 一個調(diào)用了 Thread.join 方法的線程會等待指定的線程結(jié)束。
?
對應(yīng)的英文原文如下:
A thread is in the waiting state due to calling one of the following methods:
- Object.wait with no timeout
- Thread.join with no timeout
- LockSupport.park
A thread in the waiting state is waiting for another thread to perform a particular action. For example, a thread that has called Object.wait() on an object is waiting for another thread to call Object.notify() or Object.notifyAll() on that object. A thread that has called Thread.join() is waiting for a specified thread to terminate.
線程間的協(xié)作(cooperate)機(jī)制
顯然,WAITING 狀態(tài)所涉及的不是一個線程的獨(dú)角戲,相反,它涉及多個線程,具體地講,這是多個線程間的一種協(xié)作機(jī)制。談到線程我們經(jīng)常想到的是線程間的競爭(race),比如去爭奪鎖,但這并不是故事的全部,線程間也會有協(xié)作機(jī)制。
就好比在公司里你和你的同事們,你們可能存在在晉升時的競爭,但更多時候你們更多是一起合作以完成某些任務(wù)。
wait/notify 就是線程間的一種協(xié)作機(jī)制,那么首先,為什么 wait?什么時候 wait?它為什么要等其它線程執(zhí)行“特別的動作”?它到底解決了什么問題?
wait 的場景
首先,為什么要 wait 呢?簡單講,是因?yàn)?strong>條件(condition)不滿足。那么什么是條件呢?為方便理解,我們設(shè)想一個場景:
有一節(jié)列車車廂,有很多乘客,每個乘客相當(dāng)于一個線程;里面有個廁所,這是一個公共資源,且一次只允許一個線程進(jìn)去訪問(畢竟沒人希望在上廁所期間還與他人共享~)。
競爭關(guān)系
假如有多個乘客想同時上廁所,那么這里首先存在的是競爭的關(guān)系。
如果將廁所視為一個對象,它有一把鎖,想上廁所的乘客線程需要先獲取到鎖,然后才能進(jìn)入廁所。
Java 在語言級直接提供了同步的機(jī)制,也即是 synchronized 關(guān)鍵字:
synchronized(expression) {……}
它的機(jī)制是這樣的:對表達(dá)式(expresssion)求值(值的類型須是引用類型(reference type)),獲取它所代表的對象,然后嘗試獲取這個對象的鎖:
- 如果能獲取鎖,則進(jìn)入同步塊執(zhí)行,執(zhí)行完后退出同步塊,并歸還對象的鎖(異常退出也會歸還);
- 如果不能獲取鎖,則阻塞在這里,直到能夠獲取鎖。
在一個線程還在廁所期間,其它同時想上廁所的線程被阻塞,處在該廁所對象的 entry set 中,處于 BLOCKED 狀態(tài)。
完事之后,退出廁所,歸還鎖。
之后,系統(tǒng)再在 entry set 中挑選一個線程,將鎖給到它。
對于以上過程,以下為一個 gif 動圖演示:
當(dāng)然,這就是我們所熟悉的鎖的競爭過程。以下為演示的代碼:
public void testBlockedState() throws Exception {class Toilet { // 廁所類public void pee() { // 尿尿方法try {Thread.sleep(21000);// 研究表明,動物無論大小尿尿時間都在21秒左右} catch (InterruptedException e) {Thread.currentThread().interrupt();}}} Toilet toilet = <span class="hljs-keyword">new</span> Toilet();Thread passenger1 = <span class="hljs-keyword">new</span> Thread(<span class="hljs-keyword">new</span> Runnable() {<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>{<span class="hljs-keyword">synchronized</span> (toilet) {toilet.pee();}} });Thread passenger2 = <span class="hljs-keyword">new</span> Thread(<span class="hljs-keyword">new</span> Runnable() {<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>{<span class="hljs-keyword">synchronized</span> (toilet) {toilet.pee();}} });passenger1.start();<span class="hljs-comment">// 確保乘客1先啟動</span> Thread.sleep(<span class="hljs-number">100</span>);passenger2.start();<span class="hljs-comment">// 確保已經(jīng)執(zhí)行了 run 方法</span> Thread.sleep(<span class="hljs-number">100</span>);<span class="hljs-comment">// 在乘客1在廁所期間,乘客2處于 BLOCKED 狀態(tài)</span> assertThat(passenger2.getState()).isEqualTo(Thread.State.BLOCKED);}
條件
現(xiàn)在,假設(shè)有個女乘客,她搶到了鎖,進(jìn)去之后褲子脫了一半,發(fā)現(xiàn)馬桶的墊圈紙沒了,于是拒絕尿。
或許是因?yàn)樗容^講究衛(wèi)生,怕直接坐上去會弄臟她白花花的屁股~
現(xiàn)在,條件出現(xiàn)了:有紙沒紙,這就是某種條件。
那么,現(xiàn)在條件不滿足,這位女線程改怎么辦呢?如果只是在里面干等,顯然是不行的。
這不就是人民群眾所深惡痛絕的“占著茅坑不拉尿”嗎?
- 一方面,外面 entry set 中可能好多群眾還嗷嗷待尿呢(其中可能有很多大老爺線程,他們才不在乎有沒有馬桶墊圈紙~)
- 另一方面,假定外面同時有“乘務(wù)員線程”,準(zhǔn)備進(jìn)去增加墊圈紙,可你在里面霸占著不出來,別人也沒法進(jìn)去,也就沒法加紙。
所以,當(dāng)條件不滿足時,需要出來,要把鎖還回去,以使得諸如“乘務(wù)員線程”的能進(jìn)去增加紙張。
等待是必要的嗎?
那么出來之后是否一定需要等待呢?當(dāng)然也未必。
這里所謂“等待”,指的是使線程處于不再活動的狀態(tài),即是從調(diào)度隊(duì)列中剔除。
如果不等待,只是簡單歸還鎖,用一個反復(fù)的循環(huán)來判斷條件是否滿足,那么還是可以再次回到調(diào)度隊(duì)列,然后期待在下一次被調(diào)度到的時候,可能條件已經(jīng)發(fā)生變化:
比如某個“乘務(wù)員線程”已經(jīng)在之前被調(diào)度并增加了里面的墊圈紙。自然,也可能再次調(diào)度到的時候,條件依舊是不滿足的。
現(xiàn)在讓我們考慮一種比較極端的情況:廁所外一大堆的“女乘客線程”想進(jìn)去方便,同時還有一個焦急的“乘務(wù)員線程”想進(jìn)去增加廁紙。
如果線程都不等待,而廁所又是一個公共資源,無法并發(fā)訪問。調(diào)度器每次挑一個線程進(jìn)去,挑中“乘務(wù)員線程”的幾率反而降低了,entry set 中很可能越聚越多無法完成方便的“女乘客線程”,“乘務(wù)員線程”被選中執(zhí)行的幾率越發(fā)下降。
當(dāng)然,同步機(jī)制會防止產(chǎn)生所謂的“饑餓(starvation)”現(xiàn)象,“乘務(wù)員線程”最終還是有機(jī)會執(zhí)行的,只是系統(tǒng)運(yùn)行的效率下降了。
所以,這會干擾正常工作的線程,擠占了資源,反而影響了自身?xiàng)l件的滿足。另外,“乘務(wù)員線程”可能這段時間根本沒有啟動,此時,不愿等待的“女乘客線程”不過是徒勞地進(jìn)進(jìn)出出,占用了 CPU 資源卻沒有辦成正事。
效果上還是在這種沒有進(jìn)展的進(jìn)進(jìn)出出中等待,這種情形類似于所謂的忙等待 (busy waiting)。
協(xié)作關(guān)系
綜上,等待還是有必要的,我們需要一種更高效的機(jī)制,也即是 wait/notify 的協(xié)作機(jī)制。
當(dāng)條件不滿足時,應(yīng)該調(diào)用 wait()方法,這時線程釋放鎖,并進(jìn)入所謂的 wait set 中,具體的講,是進(jìn)入這個廁所對象的 wait set 中:
這時,線程不再活動,不再參與調(diào)度,因此不會浪費(fèi) CPU 資源,也不會去競爭鎖了,這時的線程狀態(tài)即是 WAITING。
現(xiàn)在的問題是:她們什么時候才能再次活動呢?顯然,最佳的時機(jī)是當(dāng)條件滿足的時候。
之后,“乘務(wù)員線程”進(jìn)去增加廁紙,當(dāng)然,此時,它也不能只是簡單加完廁紙就完了,它還要執(zhí)行一個特別的動作,也即是“通知(notify)”在這個對象上等待的女乘客線程:
大概就是向她們喊一聲:“有紙啦!趕緊去尿吧!”顯然,如果只是“女乘客線程”方面一廂情愿地等待,她們將沒有機(jī)會再執(zhí)行。
所謂“通知”,也即是把她們從 wait set 中釋放出來,重新進(jìn)入到調(diào)度隊(duì)列(ready queue)中。
- 如果是 notify,則選取所通知對象的 wait set 中的一個線程釋放;
- 如果是 notifyAll,則釋放所通知對象的 wait set 上的全部線程。
整個過程如下圖所示:
對于上述過程,我們也給出以下 gif 動圖演示:
注意:哪怕只通知了一個等待的線程,被通知線程也不能立即恢復(fù)執(zhí)行,因?yàn)樗?dāng)初中斷的地方是在同步塊內(nèi),而此刻她已經(jīng)不持有鎖,所以她需要再次嘗試去獲取鎖(很可能面臨其它線程的競爭),成功后才能在當(dāng)初調(diào)用 wait 方法之后的地方恢復(fù)執(zhí)行。(這也即是所謂的 “reenter after calling Object.wait”,在上一個篇章中也曾詳細(xì)的討論了這一過程。)
- 如果能獲取鎖,線程就從 WAITING 狀態(tài)變成 RUNNABLE 狀態(tài);
- 否則,從 wait set 出來,又進(jìn)入 entry set,線程就從 WAITING 狀態(tài)又變成 BLOCKED 狀態(tài)。
綜上,這是一個協(xié)作機(jī)制,“女乘客線程”和“乘務(wù)員線程”間存在一個協(xié)作關(guān)系。顯然,這種協(xié)作關(guān)系的存在,“女乘客線程”可以避免在條件不滿足時的盲目嘗試,也為“乘務(wù)員線程”的順利執(zhí)行騰出了資源;同時,在條件滿足時,又能及時得到通知。協(xié)作關(guān)系的存在使得彼此都能受益。
生產(chǎn)者與消費(fèi)者問題
不難發(fā)現(xiàn),以上實(shí)質(zhì)上也就是經(jīng)典的“生產(chǎn)者與消費(fèi)者”的問題:
乘務(wù)員線程生產(chǎn)廁紙,女乘客線程消費(fèi)廁紙。當(dāng)廁紙沒有時(條件不滿足),女乘客線程等待,乘務(wù)員線程添加廁紙(使條件滿足),并通知女乘客線程(解除她們的等待狀態(tài))。接下來,女乘客線程能否進(jìn)一步執(zhí)行則取決于鎖的獲取情況。
代碼的演示:
在以下代碼中,演示了上述的 wait/notify 的過程:
public void testWaitingState() throws Exception { <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Toilet</span> </span>{ <span class="hljs-comment">// 廁所類</span><span class="hljs-keyword">int</span> paperCount = <span class="hljs-number">0</span>; <span class="hljs-comment">// 紙張</span><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">pee</span><span class="hljs-params">()</span> </span>{ <span class="hljs-comment">// 尿尿方法</span><span class="hljs-keyword">try</span> {Thread.sleep(<span class="hljs-number">21000</span>);<span class="hljs-comment">// 研究表明,動物無論大小尿尿時間都在21秒左右</span>} <span class="hljs-keyword">catch</span> (InterruptedException e) {Thread.currentThread().interrupt();}} }Toilet toilet = <span class="hljs-keyword">new</span> Toilet();<span class="hljs-comment">// 兩乘客線程</span> Thread[] passengers = <span class="hljs-keyword">new</span> Thread[<span class="hljs-number">2</span>]; <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < passengers.length; i++) {passengers[i] = <span class="hljs-keyword">new</span> Thread(<span class="hljs-keyword">new</span> Runnable() {<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>{<span class="hljs-keyword">synchronized</span> (toilet) {<span class="hljs-keyword">while</span> (toilet.paperCount < <span class="hljs-number">1</span>) {<span class="hljs-keyword">try</span> {toilet.wait(); <span class="hljs-comment">// 條件不滿足,等待</span>} <span class="hljs-keyword">catch</span> (InterruptedException e) {Thread.currentThread().interrupt();}}toilet.paperCount--; <span class="hljs-comment">// 使用一張紙</span>toilet.pee();}}}); }<span class="hljs-comment">// 乘務(wù)員線程</span> Thread steward = <span class="hljs-keyword">new</span> Thread(<span class="hljs-keyword">new</span> Runnable() {<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>{<span class="hljs-keyword">synchronized</span> (toilet) {toilet.paperCount += <span class="hljs-number">10</span>;<span class="hljs-comment">// 增加十張紙</span>toilet.notifyAll();<span class="hljs-comment">// 通知所有在此對象上等待的線程</span>}} });passengers[<span class="hljs-number">0</span>].start(); passengers[<span class="hljs-number">1</span>].start();<span class="hljs-comment">// 確保已經(jīng)執(zhí)行了 run 方法</span> Thread.sleep(<span class="hljs-number">100</span>);<span class="hljs-comment">// 沒有紙,兩線程均進(jìn)入等待狀態(tài)</span> assertThat(passengers[<span class="hljs-number">0</span>].getState()).isEqualTo(Thread.State.WAITING); assertThat(passengers[<span class="hljs-number">1</span>].getState()).isEqualTo(Thread.State.WAITING);<span class="hljs-comment">// 乘務(wù)員線程啟動,救星來了</span> steward.start();<span class="hljs-comment">// 確保已經(jīng)增加紙張并已通知</span> Thread.sleep(<span class="hljs-number">100</span>);<span class="hljs-comment">// 其中之一會得到鎖,并執(zhí)行 pee,但無法確定是哪個,所以用 "或 ||"</span> <span class="hljs-comment">// 注:因?yàn)?pee 方法中實(shí)際調(diào)用是 sleep, 所以很快就從 RUNNABLE 轉(zhuǎn)入 TIMED_WAITING(sleep 時對應(yīng)的狀態(tài))</span> assertTrue(Thread.State.TIMED_WAITING.equals(passengers[<span class="hljs-number">0</span>].getState())|| Thread.State.TIMED_WAITING.equals(passengers[<span class="hljs-number">1</span>].getState()));<span class="hljs-comment">// 其中之一則被阻塞,但無法確定是哪個,所以用 "或 ||"</span> assertTrue(Thread.State.BLOCKED.equals(passengers[<span class="hljs-number">0</span>].getState()) || Thread.State.BLOCKED.equals(passengers[<span class="hljs-number">1</span>].getState()));}
join 的場景及其它
從定義中可知,除了 wait/notify 外,調(diào)用 join 方法也會讓線程處于 WAITING 狀態(tài)。
join 的機(jī)制中并沒有顯式的 wait/notify 的調(diào)用,但可以視作是一種特殊的,隱式的 wait/notify 機(jī)制。
假如有 a,b 兩個線程,在 a 線程中執(zhí)行 b.join(),相當(dāng)于讓 a 去等待 b,此時 a 停止執(zhí)行,等 b 執(zhí)行完了,系統(tǒng)內(nèi)部會隱式地通知 a,使 a 解除等待狀態(tài),恢復(fù)執(zhí)行。
換言之,a 等待的條件是 “b 執(zhí)行完畢”,b 完成后,系統(tǒng)會自動通知 a。
關(guān)于 LockSupport.park 的情況則由讀者自行分析。
與傳統(tǒng) waiting 狀態(tài)的關(guān)系
Thread.State.WAITING 狀態(tài)與傳統(tǒng)的 waiting 狀態(tài)類似:
本文后面部分轉(zhuǎn)載自:https://my.oschina.net/goldenshaw/blog/802620
如有侵權(quán),請告知刪除
總結(jié)
以上是生活随笔為你收集整理的Java 线程状态---WAITING(部分转载)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 通过终端,查看sqlite3的存储文件
- 下一篇: iOS应用图片命名规则