一、CAS 是什么?
CAS(Compare And Swap),比较并交换,它是一条CPU并发原语。它的功能是判断内存某个位置的值是否为预期值,如果是则更新为新的值,这个过程是原子的。
1 public class CASDemo { 2 public static void main(String[] args) { 3 AtomicInteger atomicInteger = new AtomicInteger(5); 4 5 System.out.print(atomicInteger.compareAndSet(5, 2019)); 6 System.out.println(" the value are " + atomicInteger.get()); 7 8 System.out.print(atomicInteger.compareAndSet(5, 1024)); 9 System.out.println(" the value are " + atomicInteger.get()); 10 } 11 }
CAS 并发原语体现在 Java 语言中就是 sun.misc.Unsafe 类中的各个方法。调用 Unsafe 类中的 CAS 方法,JVM 会帮我们实现 CAS 汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作,再次强调,由于CAS 是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许中断,也就是说 CAS 是一条原子指令,不会造成所谓的数据不一致的问题。
二、Unsafe 类
以 AtomicInteger 原子类为例:
1 public class AtomicInteger extends Number implements java.io.Serializable { 2 private static final long serialVersionUID = 6214790243416807050L; 3 4 // setup to use Unsafe.compareAndSwapInt for updates 5 private static final Unsafe unsafe = Unsafe.getUnsafe(); 6 private static final long valueOffset; 7 8 static { 9 try { 10 valueOffset = unsafe.objectFieldOffset 11 (AtomicInteger.class.getDeclaredField("value")); 12 } catch (Exception ex) { throw new Error(ex); } 13 } 14 15 private volatile int value; 16 17 // ... 18 }
2.1 参数解析
- UnSafe是CAS的核心类。由于Java 方法无法直接访问底层,需要通过本地(native)方法来访问,UnSafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe 类在于 sun.misc 包中,其内部方法操作可以像 C 的指针一样直接操作内存,因为 Java 中 CAS 操作的执行依赖于 Unsafe 类的方法。
注意:Unsafe 类中所有的方法都是 native 修饰的,也就是说 Unsafe 类中的方法都是直接调用操作底层资源执行相应任务。 - 变量 valueOffset,便是该变量在内存中的偏移地址,因为 Unsafe 就是根据内存偏移地址获取数据的。
- 变量 value 用 volatile 修饰,保证了多线程之间的可见性。
2.2 方法示例解析
以 AtomicInteger 原子类中的 getAndIncrement() 为例:
1 /** 2 * AtomicInteger.java 类中的相当于自增的方法 3 */ 4 public final int getAndIncrement() { 5 return unsafe.getAndAddInt(this, valueOffset, 1); 6 } 7 8 // Unsafe 类中获取指定内存区域变量的值 9 public native int getIntVolatile(Object var1, long var2); 10 11 12 /** 13 * Unsafe 类中的方法 14 * @param var1 当前 AtomicInteger 对象 15 * @param var2 变量在内存中的偏移地址 16 * @param var4 变量的增量 17 * 方法解析: 18 * 首先调用 getIntVolatile() 方法获取指定内存区域的变量的值存储到 var5 中, 19 * 要进行增值操作前,再重新从该地址获取值与 var5 比较, 20 * 如果相同,则更新 var5 的值,并返回true,取反后,退出循环,返回相加后的值; 21 * 如果不相同,返回 false,循环继续,重新取值,直到更新成功 22 */ 23 public final int getAndAddInt(Object var1, long var2, int var4) { 24 int var5; 25 do { 26 var5 = this.getIntVolatile(var1, var2); 27 } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); 28 29 return var5; 30 }
三、CAS 的缺点
3.1 循环时间长导致开销变大
如果 CAS 一直获取不到值,那么就会给 CPU 带来很大的开销。
3.2 只能保证一个共享变量的原子性
对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。
3.3 ABA 问题
CAS算法实现一个重要前提是需要取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差内会导致数据的变化。
比如说一个线程 one 从内存位置 V 中取出 A,这时候另一个线程 two 也从内存中取出 A,并且线程 two 进行了一些操作将值变成了 B,然后线程 two 又将 V 位置的数据变成 A,这时候线程 one 进行CAS操作发现内存中仍然是 A,然后线程 one 操作成功。
尽管线程 one 的 CAS 操作成功,但是不代表这个过程就是没有问题的。
ABA 问题的解决可通过 java.util.concurrent.atomic.AtomicStampedReference; 带时间戳的原子引用类来解决。