Java CAS ABA問題發生的場景分析

  提到了CAS操做存在問題,就是在CAS以前A變成B又變回A,CAS仍是可以設置成功的,什麼場景下會出現這個問題呢?查了一些資料,發如今下面的兩種狀況下會出現ABA問題。java

  1.A最開始的內存地址是X,而後失效了,有分配了B,剛好內存地址是X,這時候經過CAS操做,卻設置成功了併發

  這種狀況在帶有GC的語言中,這種狀況是不可能發生的,爲何呢?拿JAVA舉例,在執行CAS操做時,A,B對象確定生命週期內,GC不可能將其釋放,那麼A指向的內存是不會被釋放的,B也就不可能分配到與A相同的內存地址,CAS失敗。若在無GC的,A對象已經被釋放了,那麼B被分配了A的內存,CAS成功。ide

  2.線程1準備用CAS將變量的值由A替換爲B,在此以前,線程2將變量的值由A替換爲C,又由C替換爲A,而後線程1執行CAS時發現變量的值仍然爲A,因此CAS成功。但實際上這時的現場已經和最初不一樣了,儘管CAS成功,但可能存在潛藏的問題。好比:atom

  現有一個用單向鏈表實現的堆棧,棧頂爲A,這時線程T1已經知道A.next爲B,而後但願用CAS將棧頂替換爲B:head.compareAndSet(A,B);在T1執行上面這條指令以前,線程T2介入,將A、B出棧,再pushD、C、A而對象B此時處於遊離狀態:此時輪到線程T1執行CAS操做,檢測發現棧頂仍爲A,因此CAS成功,棧頂變爲B,但實際上B.next爲null其中堆棧中只有B一個元素,C和D組成的鏈表再也不存在於堆棧中,無緣無故就把C、D丟掉了spa

  以上就是因爲ABA問題帶來的隱患,各類樂觀鎖的實現中一般都會用版本戳version來對記錄或對象標記,避免併發操做帶來的問題,在Java中,AtomicStampedReference<E>也實現了這個做用,它經過包裝[E,Integer]的元組來對對象標記版本戳stamp,從而避免ABA問題,例以下面的代碼分別用AtomicInteger和AtomicStampedReference來對初始值爲100的原子整型變量進行更新,AtomicInteger會成功執行CAS操做,而加上版本戳的AtomicStampedReference對於ABA問題會執行CAS失敗線程

package concur.lock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;

public class ABA {
    
    private static AtomicInteger atomicInt = new AtomicInteger(100);
    private static AtomicStampedReference<Integer> atomicStampedRef = 
            new AtomicStampedReference<Integer>(100, 0);
    
    public static void main(String[] args) throws InterruptedException {
        Thread intT1 = new Thread(new Runnable() {
            @Override
            public void run() {
                atomicInt.compareAndSet(100, 101);
                atomicInt.compareAndSet(101, 100);
            }
        });
        
        Thread intT2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                boolean c3 = atomicInt.compareAndSet(100, 101);
                System.out.println(c3);        //true
            }
        });
        
        intT1.start();
        intT2.start();
        intT1.join();
        intT2.join();
        
        Thread refT1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                atomicStampedRef.compareAndSet(100, 101, 
                        atomicStampedRef.getStamp(), atomicStampedRef.getStamp()+1);
                atomicStampedRef.compareAndSet(101, 100, 
                        atomicStampedRef.getStamp(), atomicStampedRef.getStamp()+1);
            }
        });
        
        Thread refT2 = new Thread(new Runnable() {
            @Override
            public void run() {
                int stamp = atomicStampedRef.getStamp();
                System.out.println("before sleep : stamp = " + stamp);    // stamp = 0
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("after sleep : stamp = " + atomicStampedRef.getStamp());//stamp = 1
                boolean c3 = atomicStampedRef.compareAndSet(100, 101, stamp, stamp+1);
                System.out.println(c3);        //false
            }
        });
        
        refT1.start();
        refT2.start();
    }

}
相關文章
相關標籤/搜索