您好,登錄后才能下訂單哦!
本篇文章為大家展示了Java 中怎么防止在線程阻塞與喚醒時(shí)死鎖,內(nèi)容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細(xì)介紹希望你能有所收獲。
Java并發(fā)編程:多線程如何實(shí)現(xiàn)阻塞與喚醒 說到suspend與resume組合有死鎖傾向,一不小心將導(dǎo)致很多問題,甚至導(dǎo)致整個(gè)系統(tǒng)崩潰。接著看另外一種解決方案,我們可以使用以對象為目標(biāo)的阻塞,即利用Object類的wait()和notify()方法實(shí)現(xiàn)線程阻塞。當(dāng)線程到達(dá)監(jiān)控對象時(shí),通過wait方法會使線程進(jìn)入到等待隊(duì)列中。而當(dāng)其它線程調(diào)用notify時(shí)則可以使線程重新回到執(zhí)行隊(duì)列中,得以繼續(xù)執(zhí)行
針對對象的阻塞編程思維需要我們稍微轉(zhuǎn)變下思維,它與面向線程阻塞思維有較大差異。如前面的suspend與resume只需在線程內(nèi)直接調(diào)用就能完成掛起恢復(fù)操作,這個(gè)很好理解。而如果改用wait與notify形式則是通過一個(gè)object作為信號,可以將其看成是一堵門。object的wait()方法是鎖門的動作,notify()是開門的動作。某一線程一旦關(guān)上門后其他線程都將阻塞,直到別的線程打開門。?
如圖所示,一個(gè)對象object調(diào)用wait()方法則像是堵了一扇門。線程一、線程二都將阻塞,然后線程三調(diào)用object的notify()方法打開門,準(zhǔn)確地說是調(diào)用了notifyAll()方法,notify()僅僅能讓線程一或線程二其中一條線程通過)。最終線程一、線程二得以通過。
使用wait與notify能在一定程度上避免死鎖問題,但并不能完全避免,它要求我們必須在編程過程中避免死鎖。在使用過程中需要注意的幾點(diǎn)是:
首先,wait與notify方法是針對對象的,調(diào)用任意對象的wait()方法都將導(dǎo)致線程阻塞,阻塞的同時(shí)也將釋放該對象的鎖。相應(yīng)地,調(diào)用任意對象的notify()方法則將隨機(jī)解除該對象阻塞的線程,但它需要重新獲取改對象的鎖,直到獲取成功才能往下執(zhí)行。
其次,wait與notify方法必須在synchronized塊或方法中被調(diào)用,并且要保證同步塊或方法的鎖對象與調(diào)用wait與notify方法的對象是同一個(gè)。如此一來在調(diào)用wait之前當(dāng)前線程就已經(jīng)成功獲取某對象的鎖,執(zhí)行wait阻塞后當(dāng)前線程就將之前獲取的對象鎖釋放。當(dāng)然假如你不按照上面規(guī)定約束編寫,程序一樣能通過編譯,但運(yùn)行時(shí)將拋出IllegalMonitorStateException異常,必須在編寫時(shí)保證用法正確。
最后,notify是隨機(jī)喚醒一條阻塞中的線程并讓之獲取對象鎖,進(jìn)而往下執(zhí)行,而notifyAll則是喚醒阻塞中的所有線程,讓他們?nèi)ジ偁幵搶ο箧i,獲取到鎖的那條線程才能往下執(zhí)行。
我們通過wait與notify改造前面的例子,代碼如下。改造的思想就是在MyThread中添加一個(gè)標(biāo)識變量,一旦變量改變就相應(yīng)地調(diào)用wait和notify阻塞喚醒線程。由于在執(zhí)行wait后將釋放synchronized(this)鎖住的對象鎖,此時(shí)System.out.println("running….");早已執(zhí)行完畢,System類out對象不存在死鎖問題。
wait與notify組合的方式看起來是個(gè)不錯(cuò)的解決方式,但其面向的主體是對象object,阻塞的是當(dāng)前線程,而喚醒的是隨機(jī)的某個(gè)線程或所有線程,偏重于線程之間的通信交互。假如換個(gè)角度,面向的主體是線程的話,我就能輕而易舉地對指定的線程進(jìn)行阻塞喚醒,這個(gè)時(shí)候就需要LockSupport,它提供的park與unpark方法分別用于阻塞和喚醒.而且它提供避免死鎖和競態(tài)條件,很好地代替suspend和resume組合。
用park與unpark改造上述例子,代碼如下。把主體換成線程進(jìn)行的阻塞看起來貌似比較順眼,而且由于park與unpark方法控制的顆粒度更加細(xì)小,能準(zhǔn)確決定線程在某個(gè)點(diǎn)停止,進(jìn)而避免死鎖的產(chǎn)生。例如此例中在執(zhí)行System.out.println前線程就被阻塞了,于是不存在因競爭System類out對象而產(chǎn)生死鎖,即便在執(zhí)行System.out.println后線程才阻塞也不存在死鎖問題,因?yàn)殒i已釋放。?
LockSupport類為線程阻塞喚醒提供了基礎(chǔ),同時(shí),在競爭條件問題上具有wait和notify無可比擬的優(yōu)勢。使用wait和notify組合時(shí),某一線程在被另一線程notify之前必須要保證此線程已經(jīng)執(zhí)行到wait等待點(diǎn),錯(cuò)過notify則可能永遠(yuǎn)都在等待,另外notify也不能保證喚醒指定的某線程。反觀LockSupport,由于park與unpark引入了許可機(jī)制,許可邏輯為:
park將許可在等于0的時(shí)候阻塞,等于1的時(shí)候返回并將許可減為0。
unpark嘗試喚醒線程,許可加1。
根據(jù)這兩個(gè)邏輯,對于同一條線程,park與unpark先后操作的順序似乎并不影響程序正確地執(zhí)行。假如先執(zhí)行unpark操作,許可則為1,之后再執(zhí)行park操作,此時(shí)因?yàn)樵S可等于1直接返回往下執(zhí)行,并不執(zhí)行阻塞操作。 最后,LockSupport的park與unpark組合真正解耦了線程之間的同步,不再需要另外的對象變量存儲狀態(tài),并且也不需要考慮同步鎖,wait與notify要保證必須有鎖才能執(zhí)行,而且執(zhí)行notify操作釋放鎖后還要將當(dāng)前線程扔進(jìn)該對象鎖的等待隊(duì)列,LockSupport則完全不用考慮對象、鎖、等待隊(duì)列等問題。
上述內(nèi)容就是Java 中怎么防止在線程阻塞與喚醒時(shí)死鎖,你們學(xué)到知識或技能了嗎?如果還想學(xué)到更多技能或者豐富自己的知識儲備,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。