CAS和ABA問題

CAS和ABA問題

    引言:樂觀鎖和悲觀鎖的概念

    悲觀鎖:悲觀鎖悲觀地認爲,自己執行操作的過程中一定有人修改過自己操作的值,所以在自己操作之前會加上一把鎖,synchronized就是一個悲觀鎖。

    樂觀鎖:樂觀鎖則樂觀地認爲,當自己執行操作時不會有人修改自己操作的值,所以採用不加鎖的機制,只是在操作完成的那一刻發現產生衝突,則會重新執行操作,直到成功爲止。CAS算法就是樂觀鎖的一種實現。

    一、CAS算法

    CAS(CompareAndSet):顧名思義了,十分滿足樂觀鎖的理念,比較後,再set。

    CAS算法通常有三個操作數,一個是內存中的V,一個是自己預計的A(表示操作前內存中的值),和自己要設置的值B,在設置B時,比較A和V,如果兩者相等,則直接設置,如果不等,則重試操作。

    二、AtomicInteger源碼實現機制

    AtomicInteger是一個原子的Integer,但是AtomicInteger的原子性是如何實現的呢?
    我們來看看源碼:

    第一部分,它使用volatile關鍵字來修飾value,此操作保證了value在各線程之間可見:

private volatile int value;
    第二部分,獲取操作,由於value在各線程之間可見,所以無需加鎖,直接就可以返回一個值:
public final int get() {
    return value;
}
    第三部分,實現i++操作:
public final int incrementAndGet() {
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))
            return next;
    }
}

    這裏就是一個CAS實現了,通過一個死循環,每一次循環,都會去獲取當前內存中的value,再對其進行i++操作,最後使用compareAndSet方法比較並設置value的值,如果成功會返回一個成功的值,退出循環,如果失敗則繼續重試。

    看看compareAndSet方法,是JNI(Java本地方法接口)的一個實現:

public final boolean compareAndSet(int expect, int update) {   
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

    相比起加鎖操作,他顯然更加高效。

    三、ABA問題

    看上去無懈可擊,且十分高效,滿足併發的CAS,同樣也存在有問題,ABA問題。

    設想一個場景,當線程1對值爲A的value正在進行操作,但是,線程2這時進來,將value改爲B,並在線程1結束之前,又將value改爲了A,看上去一切都沒變,這時,線程1完成了CAS操作。但是,如果value是一個鏈表呢?雖然其鏈表頭沒有變,但是不能保證,他的鏈表內容沒有發生變化。

    通常,爲了避免ABA問題的隱患,各種CAS實現會使用版本戳。AtomicStampedReference便提供了版本戳的支持,看以下的代碼,使用AtomicStampedReference進行CAS,發生ABA問題,CAS會返回false,但是使用AtomicInteger進行CAS,發生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();
    }

}

發佈了73 篇原創文章 · 獲贊 13 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章