CAS 解析
举例说明
以 AtomicInteger
原子整型类为例,一起来分析下 CAS
底层实现机制。
atomicData.incrementAndGet()
源码如下所示:
// 自增方法
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
// 额外提供的compareAndSet方法
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
我们看到了 AtomicInteger
内部方法都是基于 Unsafe
类实现的,Unsafe
类是个跟底层硬件CPU指令通讯的复制工具类,保证原子性。
解析 CAS
unsafe.compareAndSwapInt(this, valueOffset, expect, update)
所谓的 CAS,就是 Compare And Swap,对比之后交换数据。从oldValue替换为newValue。
上面的方法,有几个重要的参数:
- this,Unsafe 对象本身,需要通过这个类来获取 value 的内存偏移地址。
- valueOffset,value 变量的内存偏移地址。
- expect,期望更新的值。
- update,要更新的最新值。
如果原子变量中的 value 值(AtomicInteger中的变量)等于 expect,则使用 update 值更新 value 值并返回 true,否则返回 false。
涉及到CAS 必然存在 value 这样的属性。
valueOffset 参数
// Unsafe实例
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
// 获得value在AtomicInteger中的偏移量
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
// 实际变量的值
private volatile int value;
valueOffset
这个静态变量又是用来做什么的呢?
这时候我们就得看看它的赋值来源了。根据 API 文档,Unsafe
的 objectFieldOffset
方法可以获取成员属性在内存中的地址相对于对象内存地址的偏移量。说得简单点就是找到这个成员变量在内存中的地址,便于后续通过内存地址直接进行操作。
基于我的理解
valueOffset
定位的是内存中对象的属性的地址(创建对象时内存分配的,不变),地址用于存储实际值的地址(比如:4 或5 的存储地址)。
对象初始化时,value 为 空,而valueOffeSet
是有值的。
CAS
操作,就是将属性地址中的实际值与export
进行对比。如果相同,将属性地址指向 update 地址。从而做到原子操作。
操作方式是通过UnSafe
实现的。
volatile
对于底层应该使用了 缓存锁定(CPU如何实现原子操作(2点)),以到达只有一个线程读的效果。
由于value
变量由volatile
修饰,属性地址指向发生变化,导致value
的值进行变更,从而保证多线程下的可见性。
java中CAS操作实现
java中是无法直接使用Unsafe的CAS
方法的。
Unsafe
类中存在Unsafe.getUnsafe()
,但创建后执行报错。如果想使用只能使用反射的方式。
public class Test {
public volatile int num = 0; // 此处如果使用Integer,其结果也是 false。 Integer是封装类
public static void main(String[] args) throws Exception {
// theUnsafe 这个对象是 Unsafe的一个属性,但可以认为就是 unsafe
// 能且只能通过反射的方式获取到 unsafe对象
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe)field.get(null);
Test test = new Test();
long numOffset = unsafe.objectFieldOffset(Test.class.getDeclaredField("num"));
// offset 属性在对象中的内存偏移量,需要指定好属性在堆内存中的内存开始位置(对象初始化时就可以知道了)
// numOffset 就是是 num属性在对象中的位置
boolean result = unsafe.compareAndSwapInt(test, numOffset, 0, 1);
System.out.println("测试cas结果:" + result);
}
}
CAS 存在的难问题
- ABA情况
- 无限循环问题
- cas只能针对一个属性的值,不能修改一段代码的操作。