當多線程幫助咱們提升應用性能的同時,它同時也帶來一些問題,本文咱們將藉助幾個小例子看下兩個問題,死鎖和活鎖。java
死鎖發生在當兩個或多個線程一直在等待另外一個線程持有的鎖或資源的時候。這會致使一個程序可能會被拖垮或者直接掛掉,由於線程們都不能繼續工做了。多線程
經典的哲學家進餐
問題很是好的展現了多線程下的同步問題而且常常被用來看成死鎖的例子。併發
首先,咱們看一個簡單的Java例子來理解死鎖。性能
在這個例子中,咱們建立兩個線程,T1和T2。線程T1調用operation1,線程T2調用operation2。測試
爲了完成操做,線程T1須要先獲取到lock1再獲取到lock2,而後此時線程T2須要先獲取到lock2再獲取到lock1。所以兩個線程都在以相反的順序獲取鎖。ui
如今,咱們寫一下DeadlockExample:線程
public class DeadlockExample { private Lock lock1 = new ReentrantLock(true); private Lock lock2 = new ReentrantLock(true); public static void main(String[] args) { DeadlockExample deadlock = new DeadlockExample(); new Thread(deadlock::operation1, "T1").start(); new Thread(deadlock::operation2, "T2").start(); } public void operation1() { lock1.lock(); print("lock1 acquired, waiting to acquire lock2."); sleep(50); lock2.lock(); print("lock2 acquired"); print("executing first operation."); lock2.unlock(); lock1.unlock(); } public void operation2() { lock2.lock(); print("lock2 acquired, waiting to acquire lock1."); sleep(50); lock1.lock(); print("lock1 acquired"); print("executing second operation."); lock1.unlock(); lock2.unlock(); } // helper methods }
咱們運行一下這個例子看下輸出:設計
Thread T1: lock1 acquired, waiting to acquire lock2. Thread T2: lock2 acquired, waiting to acquire lock1.
一運行這個例子咱們就能看到程序致使了一個死鎖且永遠也退出不了。輸出日誌展現了線程T1在等待lock2,但lock2被線程T2所持有。類似的,線程T2在等待lock1,他被T1所持有。日誌
死鎖在Java中是個很常見的併發問題,由於咱們應該設計一個程序來避免潛在的死鎖條件。code
避免獲取鎖時的循環依賴問題
。tryLock
方法,來確保一個線程若是獲取不到鎖不會一直阻塞。活鎖是另外一個併發問題,它和死鎖很類似。在活鎖中,兩個或多個線程彼此間一直在轉移狀態,而不像咱們上個例子中互相等待。結果就是全部線程都不能執行它們各自的任務。
一個比較好的活鎖例子就是消息隊列。當發生異常的時候,消息消費者回滾事務並把消息放到隊列頭中,而後相同的消息又從隊列頭中被讀到,又會形成異常並再次放入到隊列頭中。如此循壞往復,消費者永遠讀不到隊列中其餘的消息。
如今咱們展現一下活鎖的狀況,咱們一樣拿上面死鎖的例子來解釋。線程T1調用operation1,線程T2調用operation2,可是咱們稍微改變的操做的邏輯。
兩個線程都須要拿到兩把鎖來完成工做,每一個線程拿到第一個鎖後都會發現拿不到第二把鎖,所以爲了讓另外一個線程先完成任務,每一個線程都會釋放第一把鎖並會嘗試再次獲取到兩把鎖。
咱們來看下下面的測試例子
public class LivelockExample { private Lock lock1 = new ReentrantLock(true); private Lock lock2 = new ReentrantLock(true); public static void main(String[] args) { LivelockExample livelock = new LivelockExample(); new Thread(livelock::operation1, "T1").start(); new Thread(livelock::operation2, "T2").start(); } public void operation1() { while (true) { tryLock(lock1, 50); print("lock1 acquired, trying to acquire lock2."); sleep(50); if (tryLock(lock2)) { print("lock2 acquired."); } else { print("cannot acquire lock2, releasing lock1."); lock1.unlock(); continue; } print("executing first operation."); break; } lock2.unlock(); lock1.unlock(); } public void operation2() { while (true) { tryLock(lock2, 50); print("lock2 acquired, trying to acquire lock1."); sleep(50); if (tryLock(lock1)) { print("lock1 acquired."); } else { print("cannot acquire lock1, releasing lock2."); lock2.unlock(); continue; } print("executing second operation."); break; } lock1.unlock(); lock2.unlock(); } // helper methods }
咱們看下運行結果:
Thread T1: lock1 acquired, trying to acquire lock2. Thread T2: lock2 acquired, trying to acquire lock1. Thread T1: cannot acquire lock2, releasing lock1. Thread T2: cannot acquire lock1, releasing lock2. Thread T2: lock2 acquired, trying to acquire lock1. Thread T1: lock1 acquired, trying to acquire lock2. Thread T1: cannot acquire lock2, releasing lock1. Thread T1: lock1 acquired, trying to acquire lock2. Thread T2: cannot acquire lock1, releasing lock2.
能看到輸出結果裏,兩個線程都在重複的獲取鎖和釋放鎖,致使兩個線程都不能完成操做。
避免活鎖咱們得觀察一下活鎖發生的條件並根據狀況提出方案,好比: