關(guān)于Java中CompareAndSwap(CAS)的一些理解

以AtomicInteger為例,jdk版本1.8

先舉個(gè)例子

public class AtomicIntegerTest {

    private static int threadCount = 10;
    private static CountDownLatch countDown = new CountDownLatch(threadCount);
    private static int count = 0;

    public static void main(String[] args) {
        Thread[] threads = new Thread[threadCount];
        for (int i = 0; i < threadCount; i++) {
            threads[i] = new Thread(new Counter());
        }
        for (int i = 0; i < threadCount; i++) {
            threads[i].start();
        }
        try {
            countDown.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("count=" + count);
    }

    private static class Counter implements Runnable {

        @Override
        public void run() {
            for (int i = 0; i < 1000; i++) {
                count++;
            }
            countDown.countDown();
        }
    }
}

在這個(gè)例子中,我們開啟了10個(gè)線程,來增加count的值,期待最后輸出的結(jié)果是10000。顯然,并不是每次運(yùn)行的結(jié)果都是10000。因?yàn)槎鄠€(gè)線程對count的修改操作并不是原子操作。

某次運(yùn)行的輸出結(jié)果

count=9262

我們可以使用AtomicInteger來解決這個(gè)問題。

public class AtomicIntegerTest {

    private static int threadCount = 10;
    private static CountDownLatch countDown = new CountDownLatch(threadCount);
    private static AtomicInteger count = new AtomicInteger(0);//原子操作類

    public static void main(String[] args) {
        Thread[] threads = new Thread[threadCount];
        for (int i = 0; i < threadCount; i++) {
            threads[i] = new Thread(new Counter());
        }
        for (int i = 0; i < threadCount; i++) {
            threads[i].start();
        }
        try {
            countDown.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("count=" + count);
    }

    private static class Counter implements Runnable {

        @Override
        public void run() {
            for (int i = 0; i < 1000; i++) {
                //原子操作
                count.getAndIncrement();
            }
            countDown.countDown();
        }
    }
}

現(xiàn)在每次打印的結(jié)果都是10000。因?yàn)锳tomicInteger類的getAndIncrement方法是原子操作。

運(yùn)行的輸出結(jié)果

count=10000

AtomicInteger類中的一些變量

    //unsafe實(shí)例,用來獲取并操作內(nèi)存的數(shù)據(jù)。
    private static final Unsafe unsafe = Unsafe.getUnsafe();

    //用來記錄偏移量,這是一個(gè)final變量
    private static final long valueOffset;

    static {
        try {
            //valueOffset默認(rèn)值是0
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    //value,存儲(chǔ)AtomicInteger的int值,該屬性需要借助volatile關(guān)鍵字保證其在線程間是可見的。
    private volatile int value;

構(gòu)造函數(shù)

/**
 * 使用0作為初始值來構(gòu)建AtomicInteger實(shí)例
 */
public AtomicInteger() {
}

/**
 * 使用指定的初始值構(gòu)建AtomicInteger實(shí)例
 * 
 * @param initialValue 初始值
 */
public AtomicInteger(int initialValue) {
    value = initialValue;
}

AtomicInteger的getAndIncrement方法

/**
  * 原子性的把當(dāng)前值加1。
  *
  * @return 返回以前的值
  */
public final int getAndIncrement() {
   return unsafe.getAndAddInt(this, valueOffset, 1);
}

Unsafe的getAndAddInt方法

/**
 * 原子性的更新一個(gè)對象在偏移量為offset處的成員變量的值,或者原子性的更新一個(gè)數(shù)組在偏移量為offset處的元素的值。
 *
 * @param o 更新成員變量的對象,或者更新元素的數(shù)組
 * @param offset 成員變量或者數(shù)組元素的偏移
 * @param delta 要增加到的量
 * @return 先前的值
 * @since 1.8
 */
public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    //do while 自旋操作
    do {
        //獲取AtomicInteger對象在內(nèi)存中偏移量為offset處的值
        v = getIntVolatile(o, offset);
    } while (!compareAndSwapInt(o, offset, v, v + delta));
    return v;
}

Unsafe的getIntVolatile方法

/** Volatile version of {@link #getInt(Object, long)}  */
public native int getIntVolatile(Object o, long offset);

Unsafe的compareAndSwapInt方法,這個(gè)方法在JNI里是借助于一個(gè)CPU指令完成的,屬于原子操作,可以保證多個(gè)線程都能夠看到同一個(gè)變量的修改值。

/**
* 如果Java變量的當(dāng)前值是expected,則原子性的把該變量的值更新為x。
* @return 成功返回true
*/
public final native boolean compareAndSwapInt(Object o, long offset,int expected, int x);

用文字?jǐn)⑹鲆幌耮etAndAddInt這個(gè)過程。我們假設(shè)現(xiàn)在只有兩個(gè)線程,一個(gè)是主線程,一個(gè)A線程

  1. 在主線程第1次進(jìn)入 do while循環(huán),執(zhí)行 v = getIntVolatile(o, offset);獲取到的AtomicInteger的value是0賦值給v。

  2. 執(zhí)行compareAndSwapInt(o, offset, v, v + delta);如果此時(shí)A線程沒有修改AtomicInteger的value,那么獲取AtomicInteger對象在內(nèi)存中偏移量為offset處的value和v相等,執(zhí)行成功,將AtomicInteger的value更新為1,返回true,循環(huán)結(jié)束。然后返回AtomicInteger的更新之前的value,就是0。

  3. 執(zhí)行compareAndSwapInt(o, offset, v, v + delta);如果此時(shí)A線程修改了AtomicInteger的value(value值為1),
    那么獲取AtomicInteger對象在內(nèi)存中偏移量為offset處的值和v已經(jīng)不相等了(內(nèi)存中的值已經(jīng)不變成1了,而v是0),執(zhí)行失敗,進(jìn)入下一次循環(huán)。

  4. 在主線程第2次進(jìn)入 do while循環(huán),執(zhí)行 v = getIntVolatile(o, offset);,因?yàn)锳線程修改了AtomicInteger的value,獲取到的AtomicInteger的value是1賦值給v。

  5. 執(zhí)行compareAndSwapInt(o, offset, v, v + delta);如果此時(shí)A線程沒有修改AtomicInteger的value,那么獲取AtomicInteger對象在內(nèi)存中偏移量為offset處的值和v相等(都是1),執(zhí)行成功,將AtomicInteger的value更新為2,返回true,循環(huán)結(jié)束。然后返回AtomicInteger的更新之前的value,就是1。

上面的循環(huán)可以保證,在不同的線程更新value值不會(huì)出現(xiàn)被覆蓋的情況。也就是說實(shí)現(xiàn)了原子性操作。

CAS 的問題

  1. ABA問題

CAS需要在操作值的時(shí)候檢查下值有沒有發(fā)生變化,如果沒有發(fā)生變化則更新,但是如果一個(gè)值原來是A,變成了B,又變成了A,那么使用CAS進(jìn)行檢查時(shí)會(huì)發(fā)現(xiàn)它的值沒有發(fā)生變化,但是實(shí)際上卻變化了。這就是CAS的ABA問題。

常見的解決思路是使用版本號(hào)。在變量前面追加上版本號(hào),每次變量更新的時(shí)候把版本號(hào)加一,那么A-B-A 就會(huì)變成1A-2B-3A。JDK從1.5開始提供了AtomicStampedReference類來解決ABA問題。這個(gè)類的compareAndSet方法作用是首先檢查當(dāng)前引用是否等于預(yù)期引用,并且當(dāng)前標(biāo)志是否等于預(yù)期標(biāo)志,如果全部相等,則以原子方式將該引用和該標(biāo)志的值設(shè)置為給定的更新值。

  1. 循環(huán)時(shí)間長開銷大
    上面我們說過如果CAS不成功,則會(huì)原地自旋(一直 do while),如果長時(shí)間自旋會(huì)給CPU帶來非常大的執(zhí)行開銷。

  2. 只能保證一個(gè)共享變量的原子操作。
    對一個(gè)共享變量執(zhí)行操作時(shí),CAS能夠保證原子操作,但是對多個(gè)共享變量操作時(shí),CAS是無法保證操作的原子性的。

Java從1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性,可以把多個(gè)變量放在一個(gè)對象里來進(jìn)行CAS操作。

參考鏈接:

  1. Java CAS 原理剖析
  2. Unsafe.java
  3. 不可不說的Java“鎖”事
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容