一、概述
CAS 是 compare and swap 的简写,即比较并交换。通过比较工作内存值(预期值A)和主物理内存的共享值V是否相同,相同则执行规定操作,否则继续比较直到主内存和工作内存的值一致为止。这个过程是原子的。
在 Java 平台上对这种操作进行了包装。在 Unsafe 类中,调用代码如下:
unsafe.compareAndSwapInt(this, valueOffset, expect, update);
它有三个参数,分别是内存位置 V,旧的预期值 A 和新的值 B。
操作时,先从内存位置读取到值,然后和预期值A比较。如果相等,则将此内存位置的值改为新值 B,返回 true。
如果不相等,说明和其他线程冲突了,则不做任何改变,返回 false。
二、CAS实现原理。
(1)通过 AtomicInteger 原子整型类来分析CAS 底层实现机制。
**1.分析getAndIncrement方法,**源码如下所示:
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
/*
* This class intended to be implemented using VarHandles, but there
* are unresolved cyclic startup dependencies.
*/
private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value");
private volatile int value;
/**
* Creates a new AtomicInteger with the given initial value.
*
* @param initialValue the initial value
*/
public AtomicInteger(int initialValue) {
value = initialValue;
}
/**
* Creates a new AtomicInteger with initial value {@code 0}.
*/
public AtomicInteger() {
}
/**
* Atomically increments the current value,
* with memory effects as specified by {@link VarHandle#getAndAdd}.
*
* <p>Equivalent to {@code getAndAdd(1)}.
*
* @return the previous value
*/
public final int getAndIncrement() {
return U.getAndAddInt(this, VALUE, 1);
}
}
getAndIncrement 调用的是 U.getAndAddInt 方法。
/**
* Atomically adds the given value to the current value of a field
* or array element within the given object {@code o}
* at the given {@code offset}.
*
* @param o object/array to update the field/element in
* @param offset field/element offset
* @param delta the value to add
* @return the previous value
* @since 1.8
*/
@HotSpotIntrinsicCandidate
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!weakCompareAndSetInt(o, offset, v, v + delta));
return v;
}
由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务即Unsafe类可以直接操作特定内存的数据。
在Unsafe类内部又调用了compareAndSetInt这个方法。
@HotSpotIntrinsicCandidate
public final native boolean compareAndSetInt(Object o, long offset,
int expected,
int x);
可以看到这是个本地方法调用。这个本地方法在 openjdk 中依次调用的 c++ 代码为:unsafe.cpp,atomic.cpp 和 atomic_windows_x86.inline.hpp。
上面的方法,有几个重要的参数:
(1)o,Unsafe 对象本身,需要通过这个类来获取 value 的内存偏移地址。
(2)offset,value 变量的内存偏移地址。
(3)expected,期望更新的值。
(4)x,要更新的最新值。
总的来说,CAS 底层是靠调用 CPU 指令集的 cmpxchg 完成的,它是 x86 和 Intel 架构中的 compare and exchange 指令。在多核的情况下,这个指令也不能保证原子性,需要在前面加上 lock 指令。
三、CAS存在的问题
1、ABA问题
当有多个线程对一个原子类进行操作的时候,某个线程在短时间内将原子类的值 A 修改为 B,又马 上将其修改为 A,此时其他线程无感知,还是会修改成功。
ABA问题发生的概率很小,一般发生了也不会产生什么问题,但是如果和实际业务有关,ABA就会产生很大的问题。
常见的解决思路是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。
2、 CAS的自旋无限循环性能问题。
在高并发场景下,CAS多线程更新的时候,会造成大量线程进行自旋,消耗CPU资源。这样会出现性能问题。
解决方案:为了解决这一个问题,Java 8提供的一个对AtomicLong改进后的一个类,LongAdder。它具备一个分段CAS的原子操作类。
解释:当某一个线程如果对一个值更新,可以看对这个值进行分段更新,每一段叫做一个Cell,在更新每一个Cell的时候,如果很难更新它的值,出现了多次 CAS失败了,无限循环自旋的时候,进行自动迁移段,它会去尝试更新别的分段Cell的值,这样的话就可以让一个线程不会盲目的CAS自旋等待一个更新分段cell的值。这有点类似于分段锁的意思。
3、只能保证一个共享变量的原子操作。
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性。
从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。
总结:
Java 的 CAS可以看做是乐观锁的一种实现方式,所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。
sun.misc.Unsafe中的递增操作就通过CAS自旋实现的。 CAS是一种无锁算法,在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。即用户模式下的线程同步,非内核模式