CAS.md

何言 2021年08月11日 84次浏览

JUC 系列之 CAS。

CAS

CAS ( compareAndSwap ) 指令:比较并交换。Java 暴露出来的,处理器提供的原子性指令之一 。

CAS 指令需要有三个操作数,分别是内存位置(在 Java 中可以简单地理解为变量的内存地址,用 V 表示)、旧的预期值(用 A 表示)和准备设置的新值(用B表示)。CAS 指令执行时,当且仅当 V 符合 A 时,处理器才会用 B 更新 V 的值,否则它就不执行更新。但是,不管是否更新了 V 的值,都会返回 V 的 旧值 (或者返是否修改成功),上述的处理过程是一个原子操作,执行期间不会被其他线程中断。

在新版 JDK 中,添加了 varHandler 这个变量句柄,使我们可以直接使用它来实现 CAS 操作。

UNSAFE

Java 中暴露的 CAS 指令的相关方法在 com.misc.Unsafe 类中,该类采用单例模式。以下是获取其实例的方法:

@CallerSensitive
public static Unsafe getUnsafe() {
    Class<?> caller = Reflection.getCallerClass();
    if (!VM.isSystemDomainLoader(caller.getClassLoader()))
        throw new SecurityException("Unsafe");
    return theUnsafe;
}

可以看到它对类加载器做了检查,只有启动类加载器加载的类才能获取,如果要在用户程序使用该操作,则需要使用反射等操作。

原子计数器

java.util.concurrent.atomic 中提供了一系列原子计数器类,该类主要维护一个变量,该变量有两个特点:

  • 使用 volatile 保证可见性和阻止指令重排
  • 通过 Unsafe 中的操作来更新或获取该变量的值,核心为调用 CAS 指令来对该变量进行操作,附带上自旋锁等操作来实现该计数器的线程安全

乐观锁

乐观锁是一种思想模型,不局限于 Java 。

乐观锁是对于数据冲突保持一种乐观态度,操作数据时不会对操作的数据进行加锁(这使得多个任务可以并行的对数据进行操作),只有到数据提交的时候才通过一种机制来验证数据是否存在冲突(一般实现方式是通过加版本号然后进行版本号的对比方式实现);

而这种加版本号和版本号对比等操作,一般需要使用 CAS 指令来完成。

在竞争不激烈的时候,乐观锁省去了对数据加锁的操作,能提高效率。但在竞争激烈的时候,乐观锁检测到冲突后,还是需要进行处理,比如升级成悲观锁等,这种情况下就比悲观锁多了一个 CAS 操作。

轻量级锁

轻量级锁是 Java 内存模型中的概念,直接通过对对象头中的锁指针进行 CAS 操作来实现加锁,具体步骤为将对象头本身存储的 对象哈希码,分代年龄等数据压入该对象操作数栈中。而将对象头数据通过 CAS 操作更新为锁对象的指针。如果此时出现竞争(则另一个线程访问该对象时发现该对象处于轻量级锁状态),则其他锁要么保持自旋不断等待锁释放,要么将整个锁升级为重量级锁,只有重量级锁才能阻塞(挂起)线程。

而对于解锁过程,同样使用 CAS 操作,将栈中的数据通过 CAS 指令交换回对象头中。

ABA 问题

如果初值为 A ,在这段期间它的值曾经被改成B,后来又被改回为 A,那 CAS 操作就会误认为它从来没有被改变过

该问题的核心不是在于数据本身,而是在于判断,如果一个线程使用 CAS 操作,而在数据被改的时候除了更新值还需要进行某些操作,则 ABA 问题会给出误判。

解决 ABA 问题的方法很简单,可以使用 版本号等操作。