CAS 全称为Compare And Swap 翻译为比较交换,作用是让CPU比较两个值是否相等,然后原子的更新某个位置的值,实现方式基于硬件平台的汇编指令,在intel的CPU中,使用的是cmpxchg指令,就是说CAS是靠硬件实现的,从而在硬件层面提升效率。
## CSA 原理
利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。
CAS操作是原子性的,所以多线程并发使用CAS更新数据时,可以不使用锁,JDK中大量使用CAS来更新数据而防止加锁来保持原子更新。
CAS操作包含三个操作数:**内存偏移量位置,预期原值,新值**。如果内存位置的值与预期值相同,那么处理器会自动将该位置值更新为新值,否则处理器不做处理。
## 源码分析
JUC包下面的类大部分都用了CAS实现原子性操作
### AtomicInteger 源码解析
```
// 使用 unsafe 类的原子操作方式
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
//计算变量 value 在类对象中的偏移量
valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
```
`valueOffset` 字段表示 ` "value"` 内存位置,在 `compareAndSwap` 方法 ,第二个参数会用到.
`偏移量`计算方式=
```
//方法相当于原子性的 ++i
public final int getAndIncrement() {
//三个参数,1、当前的实例 2、value实例变量的偏移量 3、递增的值。
return unsafe.getAndAddInt(this, valueOffset, 1);
}
//方法相当于原子性的 --i
public final int getAndDecrement() {
//三个参数,1、当前的实例 2、value实例变量的偏移量 3、递减的值。
return unsafe.getAndAddInt(this, valueOffset, -1);
}
```
`Unsafe` 调用C 语言可以通过偏移量对变量进行操作
[OpenJDK源码地址](https://download.java.net/openjdk/jdk8/promoted/b132/openjdk-8-src-b132-03_mar_2014.zip/)
目录:`openjdk\jdk\src\share\classes\sun\misc\Unsafe.java`
看代码可知 Unsafe使用了[单例模式](https://www.runoob.com/design-pattern/singleton-pattern.html)并提供getUnsafe()方法获取。
```
//获取Unsafe实例静态方法
@CallerSensitive
public static Unsafe getUnsafe() {
Class> caller = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(caller.getClassLoader()))
throw new SecurityException("Unsafe");
return theUnsafe;
}
```
```
/**
* Returns true if the given class loader is in the system domain
* in which all permissions are granted.
*/
public static boolean isSystemDomainLoader(ClassLoader loader) {
return loader == null;
}
```
只有主类[加载器]()加载的类才能调用这个方法
[Unsafe的提供 的方法]([https://www.cnblogs.com/pkufork/p/java_unsafe.html](https://www.cnblogs.com/pkufork/p/java_unsafe.html))
我们来看看getAndAddInt方法
```
/**
* Atomically adds the given value to the current value of a field
* or array element within the given object o
* at the given 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
*/
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
//通过对象以及偏移量位置获取值
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
```
```
/**
* Atomically update Java variable to x if it is currently
* holding expected.
* @return true if successful
*/
public final native boolean compareAndSwapInt(Object o, long offset,
int expected,
int x);
```
利用 Unsafe 类的 JNI compareAndSwapInt 方法实现,使用CAS实现一个原子操作更新.
## [Unsafe.app深度分析](https://segmentfault.com/a/1190000015881923)
# CAS的ABA问题及其解决方案
假设这样一种场景,当第一个线程执行CAS(V,E,U)操作。在获取到当前变量V,准备修改为新值U前,另外两个线程已连续修改了两次变量V的值,使得该值又恢复为旧值,这样的话,我们就无法正确判断这个变量是否已被修改过,如下图:
![](https://segmentfault.com/img/remote/1460000015881927?w=491&h=247)
这就是典型的CAS的ABA问题,一般情况这种情况发现的概率比较小,可能发生了也不会造成什么问题,比如说我们对某个做加减法,不关心数字的过程,那么发生ABA问题也没啥关系。但是在某些情况下还是需要防止的,那么该如何解决呢?在Java中解决ABA问题,我们可以使用以下原子类
**AtomicStampedReference类**
AtomicStampedReference原子类是一个带有时间戳的对象引用,在每次修改后,AtomicStampedReference不仅会设置新值而且还会记录更改的时间。当AtomicStampedReference设置对象值时,对象值以及时间戳都必须满足期望值才能写入成功,这也就解决了反复读写时,无法预知值是否已被修改的窘境
底层实现为: 通过Pair私有内部类存储数据和时间戳, 并构造volatile修饰的私有实例
接着看 `java.util.concurrent.atomic.AtomicStampedReference`类的compareAndSet()方法的实现:
```
private static class Pair
final T reference;
final int stamp;
//最好不要重复的一个数据,决定数据是否能设置成功,时间戳会重复
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
static
return new Pair
}
}
```
同时对当前数据和当前时间进行比较,只有两者都相等是才会执行casPair()方法,
单从该方法的名称就可知是一个CAS方法,最终调用的还是 `Unsafe`类中的 `compareAndSwapObject`方法
到这我们就很清晰 `AtomicStampedReference`的内部实现思想了,
通过一个键值对 `Pair`存储数据和时间戳,在更新时对数据和时间戳进行比较,
只有两者都符合预期才会调用 `Unsafe`的 `compareAndSwapObject`方法执行数值和时间戳替换,也就避免了ABA的问题。
```
/**
* 原子更新带有版本号的引用类型。
* 该类将整数值与引用关联起来,可用于原子的更数据和数据的版本号。
* 可以解决使用CAS进行原子更新时,可能出现的ABA问题。
*/
public class AtomicStampedReference
//静态内部类Pair将对应的引用类型和版本号stamp作为它的成员
private static class Pair
//最好不要重复的一个数据,决定数据是否能设置成功,建议时间戳
final T reference;
final int stamp;
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
//根据reference和stamp来生成一个Pair的实例
static
return new Pair
}
}
//作为一个整体的pair变量被volatile修饰
private volatile Pair
//构造方法,参数分别是初始引用变量的值和初始版本号
public AtomicStampedReference(V initialRef, int initialStamp) {
pair = Pair.of(initialRef, initialStamp);
}
....
private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe();
private static final long pairOffset = objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class);
//获取pair成员的偏移地址
static long objectFieldOffset(sun.misc.Unsafe UNSAFE,
String field, Class> klazz) {
try {
return UNSAFE.objectFieldOffset(klazz.getDeclaredField(field));
} catch (NoSuchFieldException e) {
NoSuchFieldError error = new NoSuchFieldError(field);
error.initCause(e);
throw error;
}
}
}
```
```
/**
* @param 期望(老的)引用
* @param (新的)引用数据
* @param 期望(老的)标志stamp(时间戳)值
* @param (新的)标志stamp(时间戳)值
* @return 是否成功
*/
public boolean compareAndSet(V expectedReference,V newReference,int expectedStamp,int newStamp) {
Pair
return
// 期望(老的)引用 == 当前引用
expectedReference == current.reference &&
// 期望(老的)标志stamp(时间戳)值 == 当前标志stamp(时间戳)值
expectedStamp == current.stamp &&
// (新的)引用数据 == 当前引用数据 并且 (新的)标志stamp(时间戳)值 ==当前标志stamp(时间戳)值
((newReference == current.reference && newStamp == current.stamp) ||
#原子更新值
casPair(current, Pair.of(newReference, newStamp)));
}
//当引用类型的值与期望的一致的时候,原子的更改版本号为新的值。该方法只修改版本号,不修改引用变量的值,成功返回true
public boolean attemptStamp(V expectedReference, int newStamp) {
Pair
return
expectedReference == current.reference &&
(newStamp == current.stamp ||
casPair(current, Pair.of(expectedReference, newStamp)));
}
/**
* CAS真正实现方法
*/
private boolean casPair(Pair
return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}
```
期望 Pair
```
public static void main(String[] args) {
AtomicStampedReference
Integer i = num.getReference();
int stamped = num.getStamp();
if (num.compareAndSet(i, i + 1, stamped, stamped + 1)) {
System.out.println("测试成功");
}
}
```
通过以上原子更新方法,可见 AtomicStampedReference就是利用了Unsafe的CAS方法+Volatile关键字对存储实际的引用变量和int的版本号的Pair实例进行更新。
### ConcurrentHashMap 在 JDK 1.8 的版本中,也调整为 CAS + `synchronized`