# CAS

我们希望在并发的过程中,先进行操作,如果没有其他线程争用共享数据,那么操作就成功了,否则就采取补偿措施。这种基于冲突检测的乐观的操作许多实现都不需要将线程阻塞,因此这种同步操作称为非阻塞同步。

乐观锁需要操作和冲突检测这两个步骤具备原子性,这里的原子性不能够使用互斥同步来保证,只能靠硬件来完成。硬件支持的原子性操作最典型的是: 比较并交换 ( Compare-and-Swap,CAS )。 CAS 指令需要有 3 个操作数,分别是内存地址 V 、旧的预期值 A 和新值 B 。当执行操作时,只有当 V 的值等于 A ,才将 V 的值更新为 B

因为 CAS 操作时原子性的,所以多线程并发使用 CAS 更新数据时不用锁。类似 sql 中的条件更新一样: update set id=3 from table where id=2 。因为单条 sql 执行具有原子性,如果有多个线程同时执行此 sql 语句,只有一条能更新成功(就是最后一个执行的那个)。

CAS 方式为乐观锁, synchronized 为悲观锁。因此使用 CAS 解决并发问题通常情况下性能更优。

问题:

  • ABA 问题,如果一个变量初次读取的时候是 A 值,它的值被改成了 B ,后来又被改回为 A ,那 CAS 操作就会误认为它从来没有被改变过。 J.U.C 包提供了一个带有标记的原子引用类 AtomicStampedReference 来解决这个问题,它可以通过控制变量值的版本来保证 CAS 的正确性。在变量前加上版本号,每次变量更新的时候把版本加 1,那么 A->B->A 就会变成 1A->2B->3A ,从前是比较期望的旧值和当前值,现在多比较一个版本号( compareAndSet 方法),更新时两个一起更新。大部分情况下 ABA 问题不会影响程序并发的正确性,如果需要解决 ABA 问题,改用传统的互斥同步可能会比原子类更高效
  • 自旋 CAS 如果长时间不成功,会给 CPU 带来非常大的执行开销。
  • 只能保证一个共享变量的原子操作,多了就可以用锁。

# Unsafe 类详解

Unsafe 是位于 sun.misc 包下的一个类,主要提供一些用于执行低级别不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升 Java 运行效率、增强 Java 语言底层资源操作能力方面起到了很大的作用。但由于 Unsafe 类使 Java 语言拥有了类似 C 语言指针一样操作内存空间的能力,这无疑也增加了程序发生相关指针问题的风险。在程序中过度、不正确使用 Unsafe 类会使得程序出错的概率变大,使得 Java 这种安全的语言变得不再 “安全”,因此对 Unsafe 的使用一定要慎重。

该类的构造方法是私有的,只有通过反射才能拿到其实例化对象

public void test() {
    //Unsafe 构造方法私有,不能被实例化,通过反射获得对象
    Field f = Unsafe.class.getDeclaredField("theUnsafe");
    f.setAccessible(true);
    Unsafe unsafe = (Unsafe) f.get(null);
}

Unsafe 类提供的 API 大致可分为:内存操作,CAS,Class 相关,对象操作,线程调度,系统信息获取,内存屏障,数组操作。

通过查看源码,其实 Unsafe 只提供三种 CAS 方法

//paramObject1:int 字段所在的对象,paramLong:该字段在该对象中的偏移量,paramObject2:期望的旧值,paramObject3:要更新的值
public final native boolean compareAndSwapObject(Object paramObject1, long paramLong, Object paramObject2, Object paramObject3);
public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2);
public final native boolean compareAndSwapLong(Object paramObject, long paramLong1, long paramLong2, long paramLong3);

这几个都是 native 方法,由 C++ 代码实现,都位于 unsafe.cpp 文件中

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
  //之前的代码展示我们看到,CAS函数是放在while循环中的,也就是说,返回值是一个布尔值。
  UnsafeWrapper("Unsafe_CompareAndSwapInt");
  oop p = JNIHandles::resolve(obj);

  //该函数要更新的是一个jint,所以要先根据对象引用+偏移量得到其地址,也就是对应的指针
  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);

  //很明显,Atomic::cmpxhg才是进行CAS操作(比较+替换)的那一步,x是即将更新的值,e是期望的旧值
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END

当然,Unsafe 类还提供了许多硬件级别的操作

// 获取给定的 paramField 的内存地址偏移量
public native long staticFieldOffset(Field paramField);
// 分配内存
public native long allocateMemory(long paramLong);
// 扩充内存
public native long reallocateMemory(long paramLong1, long paramLong2);
// 释放内存
public native void freeMemory(long paramLong);
// 获取数组第一个元素的偏移地址
public native int arrayBaseOffset(Class paramClass);

AQS 讲解中,我们就提到过 AQS 的部分属性,就是 cas 时需要用到的偏移值。

关于 Unsafe 类的更多知识,可以参考美团技术团队的文章:Java 魔法类:Unsafe 应用解析

# 参考

https://pdai.tech/md/java/thread/java-thread-x-juc-AtomicInteger.html

https://tech.meituan.com/2019/02/14/talk-about-java-magic-class-unsafe.html