JUC 之 比较交换 CAS

—— CAS(Compare And Swap)

没有 CAS 之前,多线程环境下不使用原子类保证线程安全 i++,只能通过 synchronized 加锁的方式,高并发多写情况下,性能影响很大;使用 CAS 之后,可以使用原子类(Atomic )保证线程安全,类似于 乐观锁

基本概念 & 底层原理

原理

  • CAS(compare and swap):比较并交换,是一条 CPU 并发原语,它包含 3 个操作数——内存位置、预期原值、更新值(主内存值、工作内存值、更新值)
    • 执行 CAS 操作的时候,将内存位置的值与预期原值比较;
    • 如果 匹配, 那么处理器会自动将 位置值 更新为 新值
    • 如果 不匹配,处理器不做任何操作,多个贤臣同时执行 CAS 操作 只有一个会成功
    • 当且仅当旧的预期值 和 内存值 相同时,将 内存值 修改为 更新值,否则什么都不做或重来,当它重来重试的这种行为称为 自旋
      在这里插入图片描述

硬件保证

  • CAS 是 JDK 提供的非阻塞原子性操作,它通过 硬件 保证了比较-更新的原子性,它是非阻塞的且自身具有原子性,效率更高,且通过硬件保证,更可靠
  • CAS 是一条 CPU 的原子指令(cmpxchg指令【compare x change】),不会造成所谓的数据不一致问题,Unsafe 提供的 CAS 方法底层实现即为 CPU 指令 cmpxchg
    • 执行cmpxchg指令时,会判断当前系统是否为多核系统,如果是就给总线加锁,只有一个线程会对总线加锁成功,加锁成功之后会执行 CAS 操作,也就是说 CAS的原子性实际上是 CPU 实现独占的,比起用 synchronized 重量级锁,这里的排他时间要短很多,所以在多线程情况下性能会更好

源码说明

    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
  • 参数说明
    • this:表示要操作的对象
    • valueOffset:表示要操作对象中属性地址的偏移量
    • expect:表示需要修改数据的期望的值
    • update:表示需要修改为的新值

Unsafe 类详解

  • 是 CAS 的核心类,由于 Java 方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe 相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe 类存在于 sun.misc 包中,其内部方法操作可以像 C 的指针一样直接操作内存,因为 Java 中 CAS 操作的执行依赖于 Unsafe 类的方法
  • Unsafe 类中的所有方法都是 native 修饰的,所以 Unsafe 类中的方法都直接调用操作系统底层资源执行相应任务
  • 变量 valueOffset,Unsafe 就是根据内存偏移地址来获取数据的
  • 变量 value 用 volatile 修饰,保证了多线程之间的内存可见性
  • AtomicInteger 类主要利用 CAS + volatile + native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升
  • CAS 并发原语体现在 Java 语言中就是 sun.misc.Unsafe 类中的各个方法。调用 Unsafe 类中的 CAS 方法,JVM 会帮我们实现出 CAS 汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。由于 CAS 是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被打断,也就是说 CAS 是一条 CPU 的原子指令,不会造成所谓的数据不一致问题

总结

  • CAS 是靠硬件实现的,从而在硬件层面提升效率,最底层还是交给硬件来保证原子性和可见性
  • 实现方式是基于硬件平台的汇编指令,在 intel 的 CPU 中(X86机器),使用的是汇编指令 cmpxchg 指令
  • 核心思想是:比较要更新变量的值V和预期值E,相等才会将V的值设置为新值 N,如果不相等自旋

缺点

  • 自旋引起的循环时间长,CPU 开销大
  • ABA 问题
    • CAS 算法实现一个重要前提是,需要取出内存中某个时刻的数据并在当下时刻比较并交换,那么在这个时间差会导致数据的变化
    • 比如一个线程A 从内存位置取出 1,另一个线程B 也从内存中取出 1,并且线程 B 进行了一些操作将 1 改为 2,然后线程B 又将 2 改回了 1,这时线程 A 进行CAS 操作发现内存中仍然是 1,预期 OK ,进行操作更改
    • 尽管线程 A 的 CAS 操作成功,但是不代表这个过程就是没有问题的

通过版本号戳记流水原子引用(AtomicStampedReference)可解决 ABA 问题

—— 原子类

基本类型原子类

  • AtomicInteger
  • AtomicBolean
  • AtomicLong

数组类型原子类

  • AtomicIntegerArray
  • AtomicLongArray
  • AtomicReferenceArray

引用类型原子类

AtomicReference

AtomicStampedReference

  • 携带版本号的引用类型原子类,可以解决 ABA 问题
  • 解决修改过几次的问题
  • 版本号流水戳原子引用

AtomicMarkableReference

  • 原子更新带有标记位的引用类型对象
  • 解决是否修改过(就是将版本号简化为 true|false,类似于一次性使用)
  • 状态戳(true、false)原子引用

对象的属性修改原子类

  • AtomicIntegerFieldUpdater
  • AtomicLongFieldUpdater
  • AtomicReferenceFieldUpdater

原子操作增强类

  • DoubleAccumulator
  • DoubleAdder
  • LongAccumulator
  • LongAdder
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序少年不秃头

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值