咱們用經典的「哲學家進餐」問題來理解死鎖的概念。五個哲學家坐在一個圓桌旁,他們一共只有五根筷子(不是五雙),每兩人中間有一根筷子,他們時而思考,時而吃飯,吃完之後把筷子放回原處,好的協調機制可讓他們每一個人均可以吃到東西,很差的協調機制,可能會致使他們都餓死。試想一種狀況,每一個哲學家都拿到一根筷子,他們都期盼着獲得別人的筷子,但他們又都不肯放棄本身手中的筷子,這時候就出現了你們相互等待對方釋放資源而本身卻不釋放已有資源的現象。這就是一種典型的死鎖。死鎖是一類很嚴重的錯誤,發生死鎖時程序自身沒法恢復,只能重啓應用,可是重啓以後死鎖可能還會發生。所以,咱們必須在編寫程序時就避免死鎖這種嚴重的錯誤。java
產生死鎖的緣由有不少種,一般遇到的有如下幾類:數據庫
咱們直接來看例子。兩個線程A、B,兩個對象a、b,線程A持有對象a的鎖,它嘗試訪問對象b,而線程B持有對象b的鎖,他嘗試訪問對象a,此時就會出現線程A、線程B互相等待對方的現象,這就是鎖順序死鎖。安全
對應的示例代碼以下,此代碼在運行過程當中就存在着極大地線程死鎖風險。spa
public class DeadLock{ private final Object left = new Object(); private final Object right = new Object(); public void leftRight(){ synchronized(left){ synchronized(right){ doSomething(); } } } public void rightLeft(){ synchronized(right){ synchronized(left){ doSomething(); } } } }
這類的死鎖實質與上面介紹的順序死鎖同樣,只不過它們比較隱蔽,只有在動態調用方法是纔會遇到。看下面銀行轉帳方法的簡單示例:線程
public void transferMoney(Account from, Account to, BigDecimal amount){ synchronized(from){ synchronized(to){ doSomething(); } } }
咱們乍看方法以爲不會產生死鎖,可是仔細觀察你會發現,from和to兩個變量的值來自於方法的參數傳遞,因此會存在這樣一種狀況:A向B轉帳時,B也剛好轉帳給A,此時極有可能產生死鎖。因此對此類死鎖問題,咱們要仔細分析,至於他的解決方法,你們能夠思考一下。code
在協做對象之間發生的死鎖更加隱蔽。例如多個線程安全的synchronized()方法,在使用時出現相互調用的狀況,一旦調用的順序出現循環,那極有可能致使死鎖。避免這種死鎖的最簡單方法就是儘量的用synchronized代碼塊取代synchronized方法,使方法儘量的變成開放調用。對象
多個線程互相持有彼此正在等待的鎖而不釋放本身持有的鎖時就會出現死鎖。當他們在相同的資源集合上等待是也會出現死鎖。好比如下狀況:線程A持有數據庫鏈接池D1的鏈接,並等待與數據庫D2的鏈接,線程B持有數據庫D2的鏈接,並等待與數據庫D1的鏈接(鏈接池越大發生這種狀況的機率越低)。ci
分析了上面的例子以後,避免線程死鎖其實就變得簡單了。咱們無非要作的就是避開那些產生死鎖的條件便可。資源
第一,當須要得到多個鎖時用一致性的順序來獲取鎖。全部須要得到多個鎖的操做,都按照一致的順序得到鎖。這樣就避免了相互等待對方釋放鎖的狀況。文檔
第二,開放調用。在調用某個方法時不須要持有鎖,這種調用叫做開放調用。這也很好理解,好比把synchronized方法移到方法內部變成同步塊,這樣調用方法時就不須要持有鎖,進入方法synchronized塊才持有鎖。這樣作代碼更加緊湊。但同時也要注意,原子性操做的代碼要封裝到一塊兒。
除此以外,儘量的減小潛在的加鎖交互機制,同時將獲取鎖時須要遵循的協議寫進文檔都是避免死鎖的方法。