CAS其实就是执行一个函数:CAS(V,E,N)直到成功为止。
CAS操作涉及到三个操作数:
V:要读写的内存地址
E:进行比较的值 (预期值)这个期望值是每次自旋的时候从V中读取出来,然后更新的时候再比较这个值和V中的值是否相等如果相等表示没有其它线程来修改,否则被修改过,继续自旋。直到成功为止。
N:拟写入的新值
// 以AtomicInteger为例
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
compareAndSwapInt是一个native方法:
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe,
jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);//将Java对象解析成JVM的oop(普通对象指针)
//根据对象p内存地址和内存地址偏移量计算拟修改对象属性的地址
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
//基于cas比较并替换,x表示拟更新的值, addr表示要操作的内存地址, e表示预期值 这是一个原子的方法
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
主要存在三个缺陷:循环时间太长、只能保证一个共享变量原子操作、ABA问题。
ABA问题演示
public static void main(String[] args) {
AtomicInteger at = new AtomicInteger(100);
AtomicStampedReference<Integer> st = new AtomicStampedReference(100, 1);
new Thread(() -> {
// 值增1
int i = at.incrementAndGet();
System.out.println("AtomicInteger after modify : " + i);
// 撤回修改 模拟 A -> B
int j = at.decrementAndGet();
System.out.println("AtomicInteger after modify redo : " + j);
// 值增1 版本号 + 1 为 2
st.compareAndSet(st.getReference(), st.getReference() + 1, st.getStamp(), st.getStamp() + 1);
System.out.println("AtomicStampedReference after modify : " + i + " version is : " + st.getStamp());
// 撤回修改 模拟 A -> B 只不过这个时候我们携带了一个版本号 版本号 + 1 为 3
st.compareAndSet(st.getReference(), st.getReference() - 1, st.getStamp(), st.getStamp() + 1);
System.out.println("AtomicStampedReference after modify redo : " + j + " version is : " + st.getStamp());
}).start();
new Thread(() -> {
// 更新为102可以成功 这个数据其实是被改过一次
at.compareAndSet(at.get(), at.get() + 2);
System.out.println("AtomicInteger after modify in other Thread: " + at.get());
// 更新为102不可以成功 获取的还是100 因为我们预期的版本号是1 也就是第一次的100 所以修改不成功
st.compareAndSet(st.getReference(), st.getReference() + 2, 1, st.getStamp() + 1);
// 没有修改成功所以版本号还是 3
System.out.println("AtomicStampedReference after modify : " + st.getReference() + " version is : " + st.getStamp());
}).start();
}