什么是 CAS

CAS(Compare And Swap,比较并交换):针对一个变量,首先比较它的内存值与某个期望值是否相同,如果相同,就给它赋一个新值的一种原子操作。
CAS 的逻辑用伪代码如下:
if (value == expectedValue)
{
value = newValue;
}
以上伪代码描述了一个由比较和赋值两阶段组成的复合操作,CAS 可以看作是它们合并后的整体——一个不可分割的原子操作,并且是直接在硬件层面保障其原子性的。
CAS可以看做是乐观锁(对比数据库的悲观、乐观锁)的一种实现方式,Java原子类中的递增操作就通过CAS自旋实现的。
CAS是一种无锁算法,在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。

理解总结

原始CAS仅能保证原子性,并不会保证可见性。
但JVM做了优化,java层面,可以保证原子性、可见性。java中的原子类(atomic类),底层都是使用了cas操作。

CAS应用

在 Java 中,CAS 操作是由 Unsafe 类提供支持的,该类定义了三种针对不同类型变量的 CAS 操作

  1. //都是 native 方法,由 Java 虚拟机提供具体实现,意味着不同的Java虚拟机对它们的实现可能会略有不同
  2. //4 个参数,分别是:对象实例、内存偏移量、字段期望值、字段新值
  3. public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
  4. public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
  5. public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
  6. //获取Unsafe实例工具类
  7. public class UnsafeFactory {
  8. /**
  9. * 获取 Unsafe 对象
  10. * @return
  11. */
  12. public static Unsafe getUnsafe() {
  13. try {
  14. Field field = Unsafe.class.getDeclaredField("theUnsafe");
  15. field.setAccessible(true);
  16. return (Unsafe) field.get(null);
  17. } catch (Exception e) {
  18. e.printStackTrace();
  19. }
  20. return null;
  21. }
  22. /**
  23. * 获取字段的内存偏移量
  24. * @param unsafe
  25. * @param clazz
  26. * @param fieldName
  27. * @return
  28. */
  29. public static long getFieldOffset(Unsafe unsafe, Class clazz, String fieldName) {
  30. try {
  31. return unsafe.objectFieldOffset(clazz.getDeclaredField(fieldName));
  32. } catch (NoSuchFieldException e) {
  33. throw new Error(e);
  34. }
  35. }

CAS缺陷

CAS 虽然高效地解决了原子操作,但是还是存在一些缺陷的,主要表现在三个方面:

  1. - 自旋 CAS 长时间地不成功,则会给 CPU 带来非常大的开销
  2. - 只能保证一个共享变量原子操作
  3. - ABA 问题

ABA问题及其解决方案

什么是ABA问题

当有多个线程对一个原子类进行操作的时候,某个线程在短时间内将原子类的值A修改为B,又马上将其修改为A,此时其他线程不感知,还是会修改成功

ABA问题的解决方案

数据库有个锁称为乐观锁,是一种基于数据版本实现数据同步的机制,每次修改一次数据,版本就会进行累加。
同样,Java也提供了相应的原子引用类AtomicStampedReference

  1. public class AtomicStampedReference<V> {
  2. private static class Pair<T> {
  3. //reference即我们实际存储的变量,stamp是版本
  4. //每次修改可以通过+1保证版本唯一性。这样就可以保证每次修改后的版本也会往上递增。
  5. final T reference;
  6. final int stamp;
  7. private Pair(T reference, int stamp) {
  8. this.reference = reference;
  9. this.stamp = stamp;
  10. }
  11. static <T> Pair<T> of(T reference, int stamp) {
  12. return new Pair<T>(reference, stamp);
  13. }
  14. }
  15. private volatile Pair<V> pair;

补充:AtomicMarkableReference可以理解为上面AtomicStampedReference的简化版,就是不关心修改过几次,仅仅关心是否修改过。因此变量mark是boolean类型,仅记录值是否有过修改