《Java线程与并发编程实践》—— 2.3 谨防活跃性问题
本節(jié)書摘來異步社區(qū)《Java線程與并發(fā)編程實(shí)踐》一書中的第2章,第2.3節(jié),作者: 【美】Jeff Friesen,更多章節(jié)內(nèi)容可以訪問云棲社區(qū)“異步社區(qū)”公眾號(hào)查看。
2.3 謹(jǐn)防活躍性問題
活躍性這個(gè)詞代表著某件正確的事情最終會(huì)發(fā)生。活躍性失敗發(fā)生在應(yīng)用程序觸及一種無法繼續(xù)執(zhí)行的狀態(tài)。在單線程的應(yīng)用程序中,無限循環(huán)就是一個(gè)例子。多線程應(yīng)用程序面臨著諸如死鎖、活鎖和餓死的額外挑戰(zhàn)。
死鎖:線程1等待線程2互斥持有的資源,而線程2也在等待線程1互斥持有的資源。兩條線程都無法繼續(xù)執(zhí)行。
活鎖:線程x持續(xù)重試一個(gè)總是失敗的操作,以致于無法繼續(xù)執(zhí)行。
餓死:線程x一直被(調(diào)度器)延遲訪問其賴以執(zhí)行的資源。或許是調(diào)度器先于低優(yōu)先級(jí)的線程執(zhí)行高優(yōu)先級(jí)的線程,而總是有一個(gè)高優(yōu)先級(jí)的線程可以執(zhí)行。餓死通常也稱為無限延遲。
死鎖會(huì)發(fā)生在synchronized關(guān)鍵字帶來的過多同步上。如果不小心,你可能就會(huì)遭遇鎖同時(shí)被多條線程競(jìng)爭(zhēng)的情形;即線程自身缺失繼續(xù)執(zhí)行的鎖,卻持有其他線程需要的鎖,同時(shí)由于其他線程持有臨界區(qū)的鎖,導(dǎo)致沒有一條線程能夠通過臨界區(qū),進(jìn)而釋放自己所持有的鎖。清單2-1就是描述該場(chǎng)景的一個(gè)典型例子。
清單2-1 一個(gè)死鎖的問題
public class DeadlockDemo {private final Object lock1 = new Object();private final Object lock2 = new Object();public void instanceMethod1(){synchronized(lock1){synchronized(lock2){System.out.println("first thread in instanceMethod1");// critical section guarded first by// lock1 and then by lock2} }}public void instanceMethod2(){synchronized(lock2){synchronized(lock1){System.out.println("second thread in instanceMethod2");// critical section guarded first by// lock2 and then by lock1}}}public static void main(String[] args){final DeadlockDemo dld = new DeadlockDemo();Runnable r1 = new Runnable(){@Overridepublic void run(){while(true){dld.instanceMethod1();try{Thread.sleep(50);}catch (InterruptedException ie){}} }};Thread thdA = new Thread(r1);Runnable r2 = new Runnable(){@Overridepublic void run(){while(true){dld.instanceMethod2();try{Thread.sleep(50);}catch (InterruptedException ie){}}}};Thread thdB = new Thread(r2);thdA.start();thdB.start();} }``` 清單2-1中線程A和線程B在不同的時(shí)間分別調(diào)用了instanceMethod1()和instanceMethod2()方法。參考下面的執(zhí)行序列:(1)線程A調(diào)用instanceMethod1(),獲取到lock1引用對(duì)象的鎖,然后進(jìn)入它外部的臨界區(qū)(但是還沒有獲取lock2引用對(duì)象的鎖)。(2)線程B調(diào)用instanceMethod2(),獲取到lock2引用對(duì)象的鎖,然后進(jìn)入它外部的臨界區(qū)(但是還沒有獲取lock1引用對(duì)象的鎖)。(3)線程A嘗試去獲取和lock2相關(guān)聯(lián)的鎖。JVM強(qiáng)制線程在內(nèi)部臨界區(qū)之外等待,由于線程B持有那個(gè)鎖。(4)線程B嘗試去獲取和lock1相關(guān)聯(lián)的鎖。JVM強(qiáng)制線程在內(nèi)部臨界區(qū)之外等待,由于線程A持有那個(gè)鎖。(5)由于其他線程持有了必要的鎖,沒有一條線程能繼續(xù)執(zhí)行。遭遇死鎖,程序(至少在這兩條線程的上下文中)就凍結(jié)住了。照下面那樣編譯清單2-1:javac DeadlockDemo.java
運(yùn)行程序:
java DeadlockDemo`
你應(yīng)該能在標(biāo)準(zhǔn)輸出流中觀測(cè)到交替打印的first thread in instance Method1和second thread in instanceMethod2信息,直到程序因死鎖而凍結(jié)。
盡管前面的例子很清晰地識(shí)別了死鎖的狀態(tài),但偵測(cè)死鎖還是不容易。舉個(gè)例子,你的代碼可能在多個(gè)類中(在多個(gè)源文件里)包含如下的環(huán)形關(guān)系:
類A的同步方法調(diào)用了類B的同步方法。
類B的同步方法調(diào)用了類C的同步方法。
類C的同步方法調(diào)用了類A的同步方法。
如果線程A調(diào)用了類A的同步方法,線程B調(diào)用了類C的同步方法,因?yàn)榫€程A還在那個(gè)方法當(dāng)中,當(dāng)線程B嘗試調(diào)用類A的同步方法時(shí),會(huì)被阻塞住。線程A會(huì)繼續(xù)執(zhí)行直至其調(diào)用類C的同步方法,然后阻塞住。死鎖發(fā)生了。
注意:
>Java語言和JVM都沒有提供一種方式來避免死鎖,所以這一任務(wù)就落到你的身上。避免死鎖最簡(jiǎn)單的方式,就是阻止同步方法或者同步塊調(diào)用其他的同步方法和同步塊。盡管這個(gè)建議能避免死鎖發(fā)生,但還是不現(xiàn)實(shí),因?yàn)?#xff0c;在你的某一個(gè)同步方法和同步塊中,很可能需要調(diào)用Java API中的同步方法,而且這個(gè)建議有點(diǎn)因噎廢食,因?yàn)楸徽{(diào)用的同步方法和同步塊很可能不會(huì)調(diào)用其他的同步方法和同步塊,從而不會(huì)導(dǎo)致死鎖發(fā)生。
總結(jié)
以上是生活随笔為你收集整理的《Java线程与并发编程实践》—— 2.3 谨防活跃性问题的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《手机测试Robotium实战教程》——
- 下一篇: 《面向对象的思考过程(原书第4版)》一1