天天看点

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());
    }
}