1.什么是CAS
CAS的全称为Compare-And-Swap他是一条CPU并发愿语
他的功能是判断内存某个位置是否为预期值,如果是则更改为新值,这个过程是原子的。
CAS并发原语体现在java语言中就是sun.misc.Unsafe类中的各个方法。调用Unsafe类中的CAS方法。JVM会帮助我们实现出CAS汇编指令 ,这是一种完全依赖于 硬件的功能,通过它实现了原子操作。由于CAS是一种系统原语,原语属于操作系统用语范畴,是有若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行的过程中不允许被打断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题
2.Unsafe
是CAS的核心类,由于java方法无发直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为java中CAS操作的执行依赖于Unsafe类的方法
Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应的任务
通过查看compareAndSet方法可以看到该方法调用的是Unsafe类中的方法
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
valueOffset,表示的是该变量在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的。
下面是用到Unsafe类中的CAS原理的方法例子
var1 AtomicInteger对象本身
var2 该对象值的引用地址
var4 需要变动的数量。
var5 是用过var1 var2找出的内存中的真实值
用该对象的当前值与var5比较,如果相同,更新var5+var4并返回true,如果不同,继续取值然后比较,直到更新完成
Unsafe类中的compareAndSwapInt,是一个本地方法,该方法位于unsafe.cpp中
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);
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
return(jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
基本原理就是先想办法拿到变量value在内存中的地址,然后通过Atomic::cmpxchg实现比较交换,其中参数x是即将更新的值,参数e是原内存的值
3.CAS的优点
相较于synchronized来说,synchronized每次只允许一个线程访问,降低了程序的并发性,而使用CAS不会降低并发性,不会每次只允许一个线程访问,而是每个线程都在循环比较更新数据,直到更新完成为止。
4.CAS缺点
1.循环时间长可能会给CPU带来很大的开销
2.只能保证一个共享变量的原子操作
3.会引出ABA问题
4.3.1什么是ABA问题
CAS算法实现的一个重要前提是需要取出内存中的某时刻的数据并在当下时刻比较替换,那么在这个时间差里会导致数据的变化。
例如A线程从内存中取出了1,这时候B线程也从内存中取出了1,但是A线程的执行时间远远小于B线程的执行时间,A线程先把1变成2写回到内存中,又把2变成1再次写回到内存中,这时候B线程开始进行CAS操作的时候发现内存中的值依然是1,然后线程B操作也会成功。但是在这期间B线程操作虽然完成了,但是并不代表这个过程是没有问题的 。
4.3.2 ABA问题的解决
使用JUC中的AtomicStampedReference<V>类进行解决,
该类的实现就是在修改的时候加上版本号的对比,在每次修改过后,更改版本号,即使最后的值相同,但是版本号不同,这样就会避免ABA问题的产生
class demo {
public static void main(String[] args) {
AtomicStampedReference<Integer> atomic = new AtomicStampedReference<>(100,1);
new Thread(()->{
int stamp = atomic.getStamp();
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomic.compareAndSet(100,101,atomic.getStamp(),atomic.getStamp()+1);
System.out.println(atomic.getReference()+"版本号:"+atomic.getStamp());
atomic.compareAndSet(101,100,atomic.getStamp(),atomic.getStamp()+1);
System.out.println(atomic.getReference()+"版本号:"+atomic.getStamp());
},"A").start();
new Thread(()->{
int stamp = atomic.getStamp();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//确保线程A已经进行了一次ABA操作
atomic.compareAndSet(100,101,stamp,stamp+1);
System.out.println(atomic.getReference()+"版本号:"+atomic.getStamp());
},"B").start();
}
}
最后可以看到第三次修改失败了,没有成功,所以就解决了ABA问题