在安全性和活躍性之間通常存在某種制衡。我們使用加鎖機制來確保安全性,但是不合理的使用加鎖,可能導致鎖順序死結;同樣我們使用線程池和訊號量來限制對資源的使用,但是這些限制行為可能會導致資源死結。
java應用程式無法從死結中恢複過來(會造成效能下降甚至沒有響應,不像資料庫有解決死結的方案,只能重啟服務),因此設計時一定要排除那些可能導致死結出現的條件;
本文介紹一些導致活躍性故障的原因以及如何避免 1:死結
所謂死結是指多個進 程因競爭資源而造成的一種僵局(互相等待),若無外力作用,這些進程都將無法向前推進;
可以看看這裡,有死結相關的介紹:http://see.xidian.edu.cn/cpp/html/2604.html
1.1鎖順序死結 兩個線程試圖以不同的順序來獲得相同的鎖,例如,鎖L和M,線程A按照L/M的順序請求鎖,線程B按照M/L的順序請求鎖,可能會出現鎖順序死結; 如果按照相同的順序請求鎖,就不會出現加鎖的依賴性,也就不會產生鎖順序死結;
public class LeftRightLock{ private final Object left=new Object(); private final Object right=new Object(); public void leftRight(){ synchronized (left){ synchronized(right){<span style="white-space:pre"></span> doSomething(); } } } public void rightLeft(){ synchronized (right){ synchronized(left){ doSomething(); } } }}
1.2動態鎖順序死結
public void transferMoney(Account fromAccount,Account toAccount,int money){ synchronized(fromAccount){ synchronized(toAccount){ doSomeThing(); } }}
可能發生死結,看起來是按照固定順序加鎖,但是實際的加鎖順序與傳入的參數有關。 如果兩個線程同時執行: transferMoney(fromAccount,toAccount,10)
transferMoney(toAccount,fromAccount,20)有可能發生死結 如何解決。要在整個程式中按照固定的順序加鎖
public static final Object tielock=new Object()public void transferMoney(Account fromAccount,Account toAccount,int money){ int fromHash=fromAccount.identityHashCode(fromAccount) int toHash=toAccount.identityHashCode(toAccount) if(fromAccount<toAccount){ synchronized(fromAccount){ synchronized(toAccount){ doSomeThing(); } } }else if(fromAccount>toAccount){ synchronized(toAccount){ synchronized(fromAccount){ doSomeThing(); } } }else{ synchronized(tielock){ synchronized(toAccount){ synchronized(fromAccount){ doSomeThing(); } } }}
1.3在協作對象之間發生死結 如果在持有鎖時調用某個外部方法,那麼將有可能出現活躍性問題。在這個外部調用中可能會擷取其他鎖(可能產生死結),或者阻塞時間過長,導致其他線程無法獲得當前持有的鎖;
class A{ private B b; public synchronized void f(){ doSomethingA(); doSomethingB(); b.method(); }}
1.4開放調用 如果在調用某個方法時不需要擷取鎖,那麼這種調用被稱為開放調用;通過開放調用可以解決上述的在協作對象間發生死結的現象; 收縮加鎖代碼塊,可能會損失操作的原子性
class A{ private B b; public void f(){ synchronized{ doSomethingA(); doSomethingB(); } b.method(); }}
2:死結的避免與診斷
2.1支援定時的鎖
2.2通過線程轉儲資訊來分析死結
3:其他活躍性危險
3.1饑餓 當線程無法訪問它所需的資源而不能繼續執行時,就發生了饑餓現象,引發饑餓現象的最常見資源就是CPU。如果在java程式對對線程的優先順序使用不當或者持有鎖的時候執行一些無法結束的結構(例如無限迴圈、無限制的等待某個資源)也可能導致線程饑餓; 應該避免使用線程優先順序,這會增加平台依賴,並導致活躍性問題,最好使用預設線程優先順序
3.2糟糕的響應性
3.3活鎖