什麼是ABA問題?

我知道CAS 樂觀鎖,就是看版本號,大於就更新,小於就重來。

ABA是什麼我就不知道了,能具體舉一個例子說明一下么?


你確定你知道CAS?

CAS:對於內存中的某一個值V,提供一個舊值A和一個新值B。如果提供的舊值V和A相等就把B寫入V。這個過程是原子性的。

CAS執行結果要麼成功要麼失敗,對於失敗的情形下一班採用不斷重試。或者放棄。

ABA:如果另一個線程修改V值假設原來是A,先修改成B,再修改回成A。當前線程的CAS操作無法分辨當前V值是否發生過變化。

關於ABA問題我想了一個例子:在你非常渴的情況下你發現一個盛滿水的杯子,你一飲而盡。之後再給杯子里重新倒滿水。然後你離開,當杯子的真正主人回來時看到杯子還是盛滿水,他當然不知道是否被人喝完重新倒滿。解決這個問題的方案的一個策略是每一次倒水假設有一個自動記錄儀記錄下,這樣主人回來就可以分辨在她離開後是否發生過重新倒滿的情況。這也是解決ABA問題目前採用的策略。


通俗來講就是你大爺還是你大爺,你大媽已經不是你大媽了^_^

舉個例子

A --&> B --&> C

假設你有個用單鏈表實現的棧,如上面所示,有個head指針指向棧頂的A

用CAS原子操作,你可能會這樣實現push和pop

push(node):
curr := head
old := curr
node-&>next = curr
while (old != (curr = CAS(head, curr, node))) {
old = curr
node-&>next = curr
}

pop():
curr := head
old := curr
next = curr-&>next
while (old != (curr = CAS(head, curr, next))) {
old = curr
next = curr-&>next
}
return curr

ABA的問題在於,pop函數中,next = curr-&>next 和 while之間,線程被切換走,然後其他線程先把A彈出,又把B彈出,然後又把A壓入,棧變成 了A --&> C,此時head還是指向A,等pop被切換回來繼續執行,就把head指向B了。

因此ABA問題的本質是內存回收的問題,對於上面的例子,就是A被彈出後,需要保證它的內存不能立即釋放(因為還有線程引用它),也就不能立即被重用。這是新手使用CAS最常見的坑,實際項目中,通常配合128位CAS、引用計數、序列號或者HazardPointer等技術來避免ABA問題。


ABA問題是指在CAS操作中帶來的潛在問題

CAS意思是 compare and swap 或者 compare and set , 對於一個要更新的變數A,我們提供一個它的舊值a 和新值 b ,如果變數A的值等於舊值 那麼更新成功, 否則失敗。

如果CAS操作是基於CPU內核的原子操作,那基本是不會出現ABA問題的,但是如果CAS本身操作不滿足原子性,則會帶來ABA問題,

比如兩個線程

  • 線程1 查詢A的值為a,與舊值a比較,
  • 線程2 查詢A的值為a,與舊值a比較,相等,更新為b值
  • 線程2 查詢A的值為b,與舊值b比較,相等,更新為a值
  • 線程1 相等,更新B的值為c

可以看到這樣的情況下,線程1 可以正常 進行CAS操作,將值從a變為c 但是在這之間,實際A值已經發了a-&>b b-&>a的轉換

仔細思考,這樣可能帶來的問題是,如果需要關注A值變化過程,是會漏掉一段時間窗口的監控

2018.1.1更新==========

今天偶然看到一個ABA問題可能帶來的問題

小明在提款機,提取了50元,因為提款機問題,

有兩個線程,同時把餘額從100變為50

線程1(提款機):獲取當前值100,期望更新為50,

線程2(提款機):獲取當前值100,期望更新為50,

線程1成功執行,線程2某種原因block了,這時,某人給小明匯款50

線程3(默認):獲取當前值50,期望更新為100,

這時候線程3成功執行,餘額變為100,

線程2從Block中恢復,獲取到的也是100,compare之後,繼續更新餘額為50!!!

此時可以看到,實際餘額應該為100(100-50+50),但是實際上變為了50(100-50+50-50)

這就是ABA問題帶來的成功提交


ABA的實際運用場景是什麼呢?


推薦閱讀:

哪個品牌的指紋鎖比較好?功耗大嗎?
凱迪仕指紋鎖質量怎麼樣?
現在的大陸市面上能買到的鎖有哪些是相對安全的?
防盜門鎖鎖一圈和鎖兩圈防盜效果有區別嗎?
超市裡所使用的防盜扣鎖是什麼原理?

TAG:Java | 鎖具 | CAS | 並發 |