Java CAS操做的ABA問題

轉載自:https://my.oschina.net/OutOfMemory/blog/792289java

CAS介紹
比較並交換(compare and swap, CAS),是原子操做的一種,可用於在多線程編程中實現不被打斷的數據交換操做,從而避免多線程同時改寫某一數據時因爲執行順序不肯定性以及中斷的不可預知性產生的數據不一致問題。算法

CAS操做基於CPU提供的原子操做指令實現,各個編譯器根據這個特色實現了各自的原子操做函數。來源維基百科:編程

C語言:由GNU提供了對應的__sync系列函數完成原子操做。 
Windows:經過WindowsAPI實現了InterLocked Functions。
C++ 11:STL提供了atomic系列函數。
JAVA:sun.misc.Unsafe提供了compareAndSwap系列函數。
C#:經過Interlocked方法實現。
Go:經過import "sync/atomic"包實現。多線程

java.util.concurrent包徹底創建在CAS之上的,藉助CAS實現了區別於synchronouse同步鎖的一種樂觀鎖。
能夠看一下AtomicInteger:併發

public final int getAndIncrement() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return current; } } public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }

其中牽扯到3個值:current,next以及當前內存中的最新值,當且僅當current和內存中的最新值相同時,纔會改變內存值爲next。函數

CAS的ABA問題
ABA問題描述:
1.進程P1在共享變量中讀到值爲A
2.P1被搶佔了,進程P2執行
3.P2把共享變量裏的值從A改爲了B,再改回到A,此時被P1搶佔。
4.P1回來看到共享變量裏的值沒有被改變,因而繼續執行。gradle

雖然P1覺得變量值沒有改變,繼續執行了,可是這個會引起一些潛在的問題。ABA問題最容易發生在lock free的算法中的,CAS首當其衝,由於CAS判斷的是指針的地址。若是這個地址被重用了呢,問題就很大了。(地址被重用是很常常發生的,一個內存分配後釋放了,再分配,頗有可能仍是原來的地址)。this

ABA問題解決方案
各類樂觀鎖的實現中一般都會用版本戳version來對記錄或對象標記,避免併發操做帶來的問題,在Java中,AtomicStampedReference也實現了這個做用,它經過包裝類Pair[E,Integer]的元組來對對象標記版本戳stamp,從而避免ABA問題。atom

下面看一下AtomicInteger和AtomicStampedReference分別執行CAS操做:spa

import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicStampedReference; public class ABASingle { public static void main(String[] args) { AtomicInteger atomicInt = new AtomicInteger(100); atomicInt.compareAndSet(100, 101); atomicInt.compareAndSet(101, 100); System.out.println("new value = " + atomicInt.get()); boolean result1 = atomicInt.compareAndSet(100, 101); System.out.println(result1); // result:true AtomicInteger v1 = new AtomicInteger(100); AtomicInteger v2 = new AtomicInteger(101); AtomicStampedReference<AtomicInteger> stampedRef = new AtomicStampedReference<AtomicInteger>( v1, 0); int stamp = stampedRef.getStamp(); stampedRef.compareAndSet(v1, v2, stampedRef.getStamp(), stampedRef.getStamp() + 1); stampedRef.compareAndSet(v2, v1, stampedRef.getStamp(), stampedRef.getStamp() + 1); System.out.println("new value = " + stampedRef.getReference()); boolean result2 = stampedRef.compareAndSet(v1, v2, stamp, stamp + 1); System.out.println(result2); // result:false } }

AtomicInteger 執行cas操做成功,AtomicStampedReference執行cas操做失敗。

這樣是否是就是說AtomicInteger存在ABA問題,根本就不能用了;確定是能夠用的,AtomicInteger處理的一個數值,全部就算出現ABA問題問題,也不會有什麼影響;可是若是這裏是一個地址(地址被重用是很常常發生的,一個內存分配後釋放了,再分配,頗有可能仍是原來的地址),比較地址發現沒有問題,但其實這個對象早就變了,這時候就可使用AtomicStampedReference來解決ABA問題。

相關文章
相關標籤/搜索