《Java线程与并发编程实践》—— 2.3 谨防活跃性问题
本節書摘來異步社區《Java線程與并發編程實踐》一書中的第2章,第2.3節,作者: 【美】Jeff Friesen,更多章節內容可以訪問云棲社區“異步社區”公眾號查看。
2.3 謹防活躍性問題
活躍性這個詞代表著某件正確的事情最終會發生。活躍性失敗發生在應用程序觸及一種無法繼續執行的狀態。在單線程的應用程序中,無限循環就是一個例子。多線程應用程序面臨著諸如死鎖、活鎖和餓死的額外挑戰。
死鎖:線程1等待線程2互斥持有的資源,而線程2也在等待線程1互斥持有的資源。兩條線程都無法繼續執行。
活鎖:線程x持續重試一個總是失敗的操作,以致于無法繼續執行。
餓死:線程x一直被(調度器)延遲訪問其賴以執行的資源。或許是調度器先于低優先級的線程執行高優先級的線程,而總是有一個高優先級的線程可以執行。餓死通常也稱為無限延遲。
死鎖會發生在synchronized關鍵字帶來的過多同步上。如果不小心,你可能就會遭遇鎖同時被多條線程競爭的情形;即線程自身缺失繼續執行的鎖,卻持有其他線程需要的鎖,同時由于其他線程持有臨界區的鎖,導致沒有一條線程能夠通過臨界區,進而釋放自己所持有的鎖。清單2-1就是描述該場景的一個典型例子。
清單2-1 一個死鎖的問題
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在不同的時間分別調用了instanceMethod1()和instanceMethod2()方法。參考下面的執行序列:(1)線程A調用instanceMethod1(),獲取到lock1引用對象的鎖,然后進入它外部的臨界區(但是還沒有獲取lock2引用對象的鎖)。(2)線程B調用instanceMethod2(),獲取到lock2引用對象的鎖,然后進入它外部的臨界區(但是還沒有獲取lock1引用對象的鎖)。(3)線程A嘗試去獲取和lock2相關聯的鎖。JVM強制線程在內部臨界區之外等待,由于線程B持有那個鎖。(4)線程B嘗試去獲取和lock1相關聯的鎖。JVM強制線程在內部臨界區之外等待,由于線程A持有那個鎖。(5)由于其他線程持有了必要的鎖,沒有一條線程能繼續執行。遭遇死鎖,程序(至少在這兩條線程的上下文中)就凍結住了。照下面那樣編譯清單2-1:javac DeadlockDemo.java
運行程序:
java DeadlockDemo`
你應該能在標準輸出流中觀測到交替打印的first thread in instance Method1和second thread in instanceMethod2信息,直到程序因死鎖而凍結。
盡管前面的例子很清晰地識別了死鎖的狀態,但偵測死鎖還是不容易。舉個例子,你的代碼可能在多個類中(在多個源文件里)包含如下的環形關系:
類A的同步方法調用了類B的同步方法。
類B的同步方法調用了類C的同步方法。
類C的同步方法調用了類A的同步方法。
如果線程A調用了類A的同步方法,線程B調用了類C的同步方法,因為線程A還在那個方法當中,當線程B嘗試調用類A的同步方法時,會被阻塞住。線程A會繼續執行直至其調用類C的同步方法,然后阻塞住。死鎖發生了。
注意:
>Java語言和JVM都沒有提供一種方式來避免死鎖,所以這一任務就落到你的身上。避免死鎖最簡單的方式,就是阻止同步方法或者同步塊調用其他的同步方法和同步塊。盡管這個建議能避免死鎖發生,但還是不現實,因為,在你的某一個同步方法和同步塊中,很可能需要調用Java API中的同步方法,而且這個建議有點因噎廢食,因為被調用的同步方法和同步塊很可能不會調用其他的同步方法和同步塊,從而不會導致死鎖發生。
總結
以上是生活随笔為你收集整理的《Java线程与并发编程实践》—— 2.3 谨防活跃性问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《手机测试Robotium实战教程》——
- 下一篇: 《面向对象的思考过程(原书第4版)》一1