前兩天看極客時間 <code>Java</code> 并發課程的時候,刷到一個概念:活鎖。死鎖,倒是不陌生,活鎖卻是第一次聽到。在介紹活鎖之前,我們先來複習一下死鎖。下面的例子模拟一個轉賬業務,多線程環境,為了賬戶金額安全,對賬戶進行了加鎖。
上述例子中,當兩個線程進入轉賬方法,線程 1 擷取賬戶 6000001 這把鎖,線程 2 鎖住了賬戶 6000002 鎖。接着當線程 1 想去擷取 6000002 的鎖時,由于這把鎖已經被線程 2 持有,線程 1 将會陷入阻塞,線程狀态轉為 BLOCKED。同理,線程 2 也是同樣狀态。
通過日志,可以看到兩個線程開始轉賬方法之後,就陷入等待。<code>synchronized</code>擷取不到鎖就會阻塞,進行等待。既然這樣,我們可以使用 <code>ReentrantLock#tryLock(long timeout, TimeUnit unit)</code>進行改造。<code>tryLock</code>若能擷取鎖,将會傳回 <code>true</code>,若不能擷取鎖将會進行等待,直到滿足下列條件:
逾時時間内擷取到了鎖,傳回 <code>true</code>
逾時時間内未擷取到鎖,傳回 <code>false</code>
中斷,抛出異常
改造後代碼如下:
上面代碼使用了 <code>while(true)</code>,擷取鎖失敗,不斷重試,直到成功。運作這個方法,運氣好點,一把就能成功,運氣不好,就會如下:
<code>transfer</code> 方法一直在運作,但是最終卻得不到成功結果,這就是個活鎖的例子。死鎖将會造成線程阻塞,程式看起來就像陷入假死一樣。就像路上碰到人,你盯着我,我盯着你,互相等待對方讓道,最後誰也過不去。

你愁啥?瞅你咋啦?而活鎖不一樣,線程不斷重複同樣的操作,但也卻執行不成功。還拿上面舉例,這次你往左一步,他往右邊一步,巧了,又碰上。然後不斷循環,最後還是誰也過不去。
圖檔來源:知乎分析死鎖這個例子,兩個線程擷取的鎖的順序不一緻,最後導緻互相需要對方手中的鎖。如果兩個線程加鎖順序一緻,所需條件就會一樣,勢必就不會産生死鎖了。我們以卡号大小為順序,每次都給卡号比較大的賬戶先加鎖,這樣就可以解決死鎖問題,代碼修改如下:
對于活鎖的例子,存在兩個問題:一是鎖的鎖逾時時間都一樣,導緻兩個線程幾乎同時釋放鎖,重試時又同時上鎖,然後陷入死循環。解決這個問題,我們可以使逾時時間不一樣,引入一定的随機性。二是這裡使用 <code>while(true)</code>,實際開發中萬萬不能這麼玩。這種情況我們需要設定最大的重試次數。
畫外音:如果重試這麼多次,一直不成功,但是業務卻想成功。現在不成功,不要傻着一直試,先放下,記錄下來,待會再重試補償呗~
活鎖的代碼可以改成如下:
總結
死鎖是日常開發中比較容易碰到的情況,我們需要小心,注意加鎖的順序。活鎖,碰到情況可能不常見,本質上我們隻需要注意設定最大的重試次數,就不會永遠陷入一直重試中。
參考連結
http://c.biancheng.net/view/4786.htmlhttps://www.javazhiyin.com/43117.html