天天看點

AtomicReference 源碼分析

JUC 并發包 中也提供了三個原子應用類型的操作類:

  • AtomicReference
  • AtomicMarkableReference
  • AtomicStampedReference

有時候原子操作不一定是基本資料類型,而是 BigDecimal 類型,是以需要用到這些類。

AtomicReference 在聲明的時候需要指定一個泛型

AtomicReference<BigDecimal>

,和 AtomicInteger 是方式不一樣,底層調用的是

Unsafe#compareAndSwapObject

方法。

下面是一個使用例子:

// DecimalAccount 是一個自定義接口,隻有 getBalance 和 withdraw 抽象方法。
class DecimalAccountSafeCas implements DecimalAccount {
    AtomicReference<BigDecimal> ref;

    public DecimalAccountSafeCas(BigDecimal balance) {
        ref = new AtomicReference<>(balance);
    }

    @Override
    public BigDecimal getBalance() {
        return ref.get();
    }
    
    @Override
    public void withdraw(BigDecimal amount) {
        while (true) {
            BigDecimal prev = ref.get();
            BigDecimal next = prev.subtract(amount);
            if (ref.compareAndSet(prev, next)) {
                break;
            }
        }
    }
}
           

AtomicReference 就是修改引用所指向的對象執行個體。

用來解決 ABA 問題,并且可以擷取到值被更改了多少次。

下面是一個例子:

static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);

    public static void main(String[] args) throws InterruptedException {
        log.debug("main start...");
        // 擷取值 A
        String prev = ref.getReference();
        // 擷取版本号
        int stamp = ref.getStamp();
        log.debug("版本 {}", stamp);
        // 如果中間有其它線程幹擾,發生了 ABA 現象
        other();
        sleep(1);
        // 嘗試改為 C
        log.debug("change A->C {}", ref.compareAndSet(prev, "C", stamp, stamp + 1));
    }

    private static void other() {
        new Thread(() -> {
            log.debug("change A->B {}", ref.compareAndSet(ref.getReference(), "B",
                    ref.getStamp(), ref.getStamp() + 1));
            log.debug("更新版本為 {}", ref.getStamp());
        }, "t1").start();
        sleep(0.5);
        new Thread(() -> {
            log.debug("change B->A {}", ref.compareAndSet(ref.getReference(), "A",
                    ref.getStamp(), ref.getStamp() + 1));
            log.debug("更新版本為 {}", ref.getStamp());
        }, "t2").start();
    }
           

下面是針對 compareAndSet 方法的源碼分析:

public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        // pair 就是目前的值
        Pair<V> current = pair;
        return
            // 舊資料與目前資料是否相等。如果是基礎資料類型,則比較值;如果是引用資料類型,則判斷的是記憶體位址。
            expectedReference == current.reference &&
            // 舊版本号是否與目前的版本号相等。
            expectedStamp == current.stamp &&
            // 上面這兩個條件,有一個不成立,就說明資料已經被别的線程已經修改過了。

            // 下面這兩個條件就是為了防止做無用的 CAS 操作;也就是說,新資料、新版本号和目前的資料、版本号都是一樣的,也沒有别要再做 CAS 操作了。
            ((newReference == current.reference &&
              newStamp == current.stamp) ||

             // 上面這四個條件,就是為了防止無用的 CAS 操作,提高性能的;而下面這個方法才是主要做 CAS 操作的方法。
             casPair(current, Pair.of(newReference, newStamp)));
    }


    private boolean casPair(Pair<V> cmp, Pair<V> val) {
        // 多線程環境下,該方法有可能也會失敗。
        // 這裡就是再修改引用本身。
        return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
    }
           
AtomicStampedReference 的實作完全依賴它的私有靜态内部類 Pair,Pair 有兩個屬性:reference 用來儲存資料,stamp 用啦儲存版本。

class GarbageBag {
    String desc;
    public GarbageBag(String desc) {
        this.desc = desc;
    }
    public void setDesc(String desc) {
        this.desc = desc;
    }
    @Override
    public String toString() {
        return super.toString() + " " + desc;
    }
}

public class TestABAAtomicMarkableReference {
    public static void main(String[] args) throws InterruptedException {
        GarbageBag bag = new GarbageBag("裝滿了垃圾");
        // 參數2 mark 可以看作一個标記,表示垃圾袋滿了
        AtomicMarkableReference<GarbageBag> ref = new AtomicMarkableReference<>(bag, true);
        log.debug("主線程 start...");
        GarbageBag prev = ref.getReference();
        log.debug(prev.toString());
        new Thread(() -> {
            log.debug("打掃衛生的線程 start...");
            bag.setDesc("空垃圾袋");
            while (!ref.compareAndSet(bag, bag, true, false)) {}
            log.debug(bag.toString());
        }).start();
        Thread.sleep(1000);
        log.debug("主線程想換一隻新垃圾袋?");
        boolean success = ref.compareAndSet(prev, new GarbageBag("空垃圾袋"), true, false);
        log.debug("換了麼?" + success);
        log.debug(ref.getReference().toString());
    }
}