CAS的ABA问题

乐观锁、独占锁

独占锁:是一种悲观锁;独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。
乐观锁:每次不加锁,假设没有冲突去完成某项操作,如果因为冲突失败就重试,直到成功为止。
乐观锁用到的机制就是CAS,Compare and Swap。

cas介绍

CAS的全称是compare and swap
CAS的原理其实很简单,为了保证:在多线程环境下,一个线程在更新某个对象的时候,没有其他的线程对该对象进行修改。
在线程更新某个对象(或值)之前,先保存更新前的值,然后在实际更新的时候传入之前保存的值,进行比较,如果一致的话就进行更新,否则失败。

cas为什么是无锁的

在大多数处理器的指令中,都会实现 CAS 相关的指令,这一条指令就可以完成“比较并交换”的操作,也正是由于这是一条(而不是多条)CPU 指令,所以 CAS 相关的指令是具备原子性的,这个组合操作在执行期间不会被打断,这样就能保证并发安全。由于这个原子性是由== CPU 保证==的,所以无需我们程序员来操心。

什么是cas的ABA问题

定义

并发编程种的 ABA 问题:
因为 CAS 需要在操作值的时候,检查某地址的内容有没有发生变化(和旧值进行比较),如果没有发生变化则更新为新的值。但是如果一个值原来是A,变成了 B,又变成了 A,那么使用 CAS 进行检查时会发现它的值没有发生变化,但是实际上却变化了。即:这个线程在操作的时候,其他线程也在进行操作。

原因

CAS算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较并替换,那么在这个时间差类会导致数据的变化。

范例

比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。如果链表的头在变化了两次后恢复了原值,但是不代表链表就没有变化。

如何解决ABA问题

解决方案:
ABA 问题的解决思路就是使用版本号。
思路很简单:每次compareAndSwap后给数据的版本号加1,下次compareAndSwap的时候不仅比较数据,也比较版本号;值相同,版本号不同也不能执行成功。那么 A→B→A 就会变成 1A→2B→3A

范例

甲乙线程想改变三角形 A 的形状,乙线程先改成了四边形,后又改成了三角形,三角形 A1->四边形 V2-> 三角形 A3。
当甲线程想改变 A3 为五边形时报错,因为三角形经过已线程修改后,前后版本号不一样,被判定为已修改过,其他线程不能修改。这样就防止了 ABA 问题。
在这里插入图片描述

java中的实现

  • 如何每次都让版本号更新的?
    AtomicStampedReference 内部维护了一个 Pair的数据结构,用volatile修饰,保证可见性,用于打包数据对象和版本号.
private static class Pair<T> {
    final T reference;
    final int stamp;
    private Pair(T reference, int stamp) {
        this.reference = reference;
        this.stamp = stamp;
    }
}

它的compareAndSet方法如下:
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)));
}

private boolean casPair(Pair<V> cmp, Pair<V> val) {
    return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}
  • 分析
    1》首先判断传入的参数是否符合 Pair 的预期,从数据和版本号两个方面来判断,有一个不符合就打回;
    2》如果符合预期,且传入的新的参数与Pair中的一样:直接返回true,不用更新;
    3》如果符合预期,新的参数和Pair中不一样:则 使用casPair来比较交换当前的Pair与传入参数构成的Pair;casPair又调用compareAndSwapObject来交互Pair属性。

  • 总结
    AtomicStampedReference是通过加版本号来解决CAS的ABA问题。至于怎么加版本号,因为compareAndSwapObject只能对比交互一个对象,所以只需要将数据和版本号打包到一个对象里就解决问题了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值