这篇博客主要讲解两个问题:
1,Java当中CAS底层实现
2,CAS的ABA问题和解决办法

Java当中CAS底层实现

首先我们来看JDK1.7的AtomicInteger类的incrementAndGet源码:

private volatile int value;
public final int get() {
        return value;
}
public final int incrementAndGet() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return next;
        }
    }

这段代码是一个无限循环,也就是CAS的自旋操作。循环体中做了3件事情:
1,获取当前变量的值;
2,对当前值做+1操作,得到目标值;
3,进行CAS操作,如果成功则跳出循环,失败则重复上述操作。
volatile保证了获取的当前值是内存中最新的值。

接下来看看compareAndSet方法时如何保证原子性的:

private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;

static {
   try {
     valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
   } catch (Exception ex) { throw new Error(ex); }
 }
 
public final boolean compareAndSet(int expect, int update) {
   return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

compareAndSet方法的实现很简单,只有一行代码,这里涉及到两个重要对象,一个是unsafe,一个是vauleOffset。
1,什么是unsafe呢?Java语言不像C,C++那样可以直接访问底层操作系统,但是JVM为我们提供了一个后门,这个后门就是unsafe。unsafe为我们提供了硬件级别的原子操作。

2,至于valueOffset对象,是通过unsafe.objectFiledOffset方法得到,所代表的是AtomicInteger对象value成员变量在内存中的偏移量。我们简单的把valueOffset理解为value变量的内存地址。

3,上一期我们提到了CAS机制使用的3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。而unsafe的compareAndSwapInt方法参数包括了这三个基本元素:valueOffset代表V,expect代表A,update代表B。正式unsafe的compareAndSwapInt方法保证了Compare和Swap操作之间的原子性操作。

ABA问题

举个提款机的例子:小明有200元存款,想用提款机取100元

由于提款机硬件出了点问题,小明的提款操作被同时提交了2次,开启了两个线程,2个线程都是获取当前值200,要更新成100。理想情况下,应该一个线程更新成功,一个线程更新失败,小明的存款只被扣一次。

ABAC java 实现 java中aba问题_Java


线程1首先执行成功,余额变为100,线程2由于某种原因被阻塞了,这个时候小明的妈妈刚好给小明汇款100元。

ABAC java 实现 java中aba问题_ABAC java 实现_02


线程2仍然阻塞,线程3执行成功,把余额从100变成200

ABAC java 实现 java中aba问题_ABA问题_03


线程2恢复运行,由于阻塞之前获取的当前值是200,而此时内存地址中的值也是200,compare检测成功,执行更新操作变成了100。

ABAC java 实现 java中aba问题_ABAC java 实现_04


原本线程2应该执行失败,小明的余额应该为200,但是由于ABA问题提交成功了。

如何解决ABA问题呢?解决办法就是加入版本号。

我们在Compare阶段不仅要比较期望值A和地址V中的值,我们还要比较变量的版本号是否一致

举个例子:

假设地址V中存储着变量值A,当前版本号是01,线程1获取当前值A和版本号01,想要更新为B,但是被阻塞了。

ABAC java 实现 java中aba问题_ABAC java 实现_05


这个时候,内存地址V中的变量发生了多次改变,版本号提升为03,但是变量值仍然是A。

ABAC java 实现 java中aba问题_CAS_06


线程1恢复运行后进行compare,泛型当前值为A,但是版本号为03与01不相等,所以这次更新失败。

ABAC java 实现 java中aba问题_CAS_07

总结:

1,Java语言CAS底层如何实现?
利用unsafe提供了原子性操作方法。
2,什么是ABA问题?怎么解决?
当一个值从A更新为B,又从B更新为A,不同的CAS机制会误判导致通过检测;利用版本号比较可以有效的解决ABA问题。