049_java.util.concurrent.atomic.AtomicReference

简单介绍

之前我们介绍了AtomicInteger的源码,相当于是对Integer的一些封装,其内部使用unsafe执行原子化操作。在这个基础之上提供了+1,-1的针对数字的操作。那么针对一般化对象呢?这个时候我们就能用上AtomicReference作为一般引用的原子操作类了。这个类提供的cas操作对比的是对象的引用。我们把常用的方法拉出来看看:

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

你会发现,和我们之前接触的AtomicInteger没什么大的差别。因此AtomicReference相关的源码解析我们就忽略了。但是除此之外我们残留一个问题并没有解决,也就是ABA问题。
ABA问题,简单的说就是一个线程将引用从A变更到B再变更到A,但是另一个线程并没有感知到这个过程。一般情况下没啥问题,就怕A-B-A的过程中把A内的数据给变更了这将会导致数据错乱。举个例子:A-B-C的链表,Thread1干的事儿是将取出A,将B删除,Thread2将A替换为D,这个时候就有可能变成D-B-C而不是D-C。

为了解决上述的问题,JDK也有现成的方案,一个是AtomicMarkableReference�,另一个是AtomicStampedReference。AtomicMarkableReference使用布尔值来区别新旧值,而AtomicStampedReference则是使用版本号来控制新旧。

AtomicMarkableReference

AtomicMarkableReference使用一个布尔值来控制是否是旧值,如下存在一个pair的内部类,将我们的引用与布尔值进行关联存储。

private volatile Pair<V> pair;
private static class Pair<T> {
    final T reference;
    final boolean mark;
    private Pair(T reference, boolean mark) {
        this.reference = reference;
        this.mark = mark;
    }
    static <T> Pair<T> of(T reference, boolean mark) {
        return new Pair<T>(reference, mark);
    }
}


AtomicMarkableReference存在pair的字段存储数据,可以利用以下几个方法获得pair中的数据:

public V getReference() {
    return pair.reference;
}    

public boolean isMarked() {
    return pair.mark;
}

如此一来,我们在使用AtomicMarkableReference进行cas的时候,就需要同时传递mark值进行判断:

// 原子操作,expectedReference,expectedMark用来匹配是否满足cas的条件
public boolean compareAndSet(V expectedReference,
                             V       newReference,
                             boolean expectedMark,
                             boolean newMark) {
    // 读取最新数据
    Pair<V> current = pair;
    return
        // 引用相等
        expectedReference == current.reference &&
        // 标记相等
        expectedMark == current.mark &&
        ((newReference == current.reference &&
          newMark == current.mark) ||
         // 替换pair
         casPair(current, Pair.of(newReference, newMark)));
}

// set方法,注意,并非是原子操作
public void set(V newReference, boolean newMark) {
    Pair<V> current = pair;
    if (newReference != current.reference || newMark != current.mark)
        this.pair = Pair.of(newReference, newMark);
}

// expectedReference匹配的时候,更新标记
public boolean attemptMark(V expectedReference, boolean newMark) {
    Pair<V> current = pair;
    return
        expectedReference == current.reference &&
        (newMark == current.mark ||
         casPair(current, Pair.of(expectedReference, newMark)));
}

private boolean casPair(Pair<V> cmp, Pair<V> val) {
    return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}

AtomicStampedReference�

AtomicStampedReference解决ABA问题更加常态化,其内部使用版本号进行控制。毕竟AtomicMarkableReference仅仅存在true/false两种状态。类似于AtomicMarkableReference,当前类也是使用Pair封装了引用与版本号:

private static class Pair<T> {
    final T reference;
    final int stamp;
    private Pair(T reference, int stamp) {
        this.reference = reference;
        this.stamp = stamp;
    }
    static <T> Pair<T> of(T reference, int stamp) {
        return new Pair<T>(reference, stamp);
    }
}

private volatile Pair<V> pair;

因此我们在更新数据的时候,必须使用引用与版本号进行同时匹配才能进行cas:

// 当expectedReference,expectedStamp匹配的时候,更新newReference,newStamp
// 如果更新成功则返回true
public boolean compareAndSet(V   expectedReference,
                             V   newReference,
                             int expectedStamp,
                             int newStamp) {
    Pair<V> current = pair;
    return
        expectedReference == current.reference &&
        expectedStamp == current.stamp &&
        ((newReference == current.reference &&
          newStamp == current.stamp) ||
         casPair(current, Pair.of(newReference, newStamp)));
}
// 注意这里没有原子操
public void set(V newReference, int newStamp) {
    Pair<V> current = pair;
    if (newReference != current.reference || newStamp != current.stamp)
        this.pair = Pair.of(newReference, newStamp);
}

// expectedReference匹配的场合下,更新版本号
public boolean attemptStamp(V expectedReference, int newStamp) {
    Pair<V> current = pair;
    return
        expectedReference == current.reference &&
        (newStamp == current.stamp ||
         casPair(current, Pair.of(expectedReference, newStamp)));
}

private boolean casPair(Pair<V> cmp, Pair<V> val) {
    return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}

总结

AtomicInteger是数值对象的线程安全封装,将数值对象扩充到一般对象就可以使用AtomicReference来搞定,但是需要注意的是,使用cas处理并发问题有可能会导致ABA问题,JDK内自带两个解决这个问题的类,其本质思路都是使用版本进行控制。其中AtomicMarkableReference使用的是布尔值来控制,AtomicStampedReference则更加宽泛,使用数字作为版本号,每次进行cas更新的时候,都需要使用既有的版本号与引用进行匹配,匹配成功之后才能执行更新操作

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值