JUC
CAS你知道吗
CAS(Compare and swap),即比较并交换,也是实现我们平时所说的自旋锁或乐观锁的核心操作
它的实现很简单,就是用一个预期的值和内存值进行比较,如果两个值相等,就用预期的值替换内存值,并返回
true。否则,返回false。
比较并交换
1 | /** |
CAS底层原理是什么 谈谈对Unsafe的理解
底层原理:
- 自旋锁
- UnSafe类
atomicInteger.getAndIncrement();
源码:
1 | /** |
UnSafe
是CAS的核心类,由于Java方法无法直接访问底层系统。需要通过本地(native)方法来访问,UnSafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在于 sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法。
注意⚠️:Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中所有的方法都直接调用操作系统底层资源执行相应任务。
CAS是什么
全称为Compare-And-Swap,是一条CPU并发原语。
它的功能是判断内存某个未知的值是否为预期值,如果是,则更改为新的值,这个过程是原子的。
CAS并发原语体现在Java语言中就是sun.misc.Unsafe类中的各个方法。强调Unsafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。
假设线程A和线程B两个线程同时执行getAndAddInt操作;
- AtomicInteger的里面的value原始值为3,即主内存中AtomicInteger的value为3,根据JMM模型,线程A和线程B各自持有一份value = 3的副本分别到各自的工作内存。
- 线程A通过getIntVolatile(var1, var2)拿到value值3,这是线程A被挂起。
- 线程B也通过getIntVolatile(var1, var2)拿到value值3,此时刚好线程B没有被挂起,并执行compareAndSwapInt方法,比较内存值也为3,成功修改内存值为4,线程B结束。
- 这时线程A恢复,执行从没怕热A你的SWAPInt方法,发现工作内存中的值 3 与主内存中的值 4 不一致,说明其他线程已经抢先一步修改过了,那么线程A本次修改失败,只能重新再读取一遍。
- 线程A重新获取主内存中Value,因为变量value被Volatile修饰,所以其他线程对他的修改线程A总能看到,线程A继续执行compareAndSwapInt进行比较替换,直到成功。
底层汇编
1 | UNSAFE_ENTRY(jboolean,Unsafe_CompareAndSwapInt(JNIEnv *env,jobject unsafe,jobject obj,jlong offset,jint e,jint x)) |
CAS缺点
在源码中,我们可以看到getAndAddInt方法,有个do while loop。
1 | public final int getAndAddInt(Object var1, long var2, int var4) { |
如果CAS失败,会一直尝试。如果CAS长时间不成功,可能会给CPU带来很大的开销。
当对一个共享变量进行操作时,我们可以使用CAS的方式来保证原子操作
但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。