【Java 并发编程】CAS 原理解析

这篇具有很好参考价值的文章主要介绍了【Java 并发编程】CAS 原理解析。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. 什么是 CAS?

1.1 悲观锁与乐观锁

悲观锁的原理是每次实现数据库的增删改的时候都进⾏阻塞,防⽌数据发⽣脏读。

乐观锁的原理是在数据库更新的时候,⽤⼀个 version 字段来记录版本号,然后通过⽐较是不是⾃⼰要修改的版本号再进⾏修改。这其中就引出了⼀种⽐较交换的思路来实现数据的⼀致性,事实上,CAS 也是基于这样的原理。

1.2 CAS 是什么?

CAS 是指 Compare And Swap比较并交换,是一种无锁原子算法,在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。

JAVA 底层是 C++ 实现,映射到操作系统就是一条 cmpxchg 硬件汇编指令(保证原子性),其作用就是让 CPU 将内存值更新为新值,但是有个条件,内存值必须与期望值相同。并且 CAS 操作无需用户态与内核态切换,直接在用户态对内存进行读写操作(意味着不会阻塞/线程上下文切换)。

java.util.concurrent.atomic 包中的原子类就是通过 CAS 来实现了乐观锁。

CAS 算法涉及到三个操作数:

  • 需要更新的内存变量值 V(volatile)

  • 上一次从内存中读取,进行比较的预期原值 E(except)

  • 要写入的新值 N(new)

当且仅当 V 的值等于 E 时,CAS 通过原子方式用新值 N 来更新 V 的值(“比较 + 更新” 整体是一个原子操作),否则不会执行任何操作,这就是一次 CAS 的操作。一般情况下,“更新” 是一个不断重试的操作。

【Java 并发编程】CAS 原理解析
简单说,CAS 需要你额外给出一个期望值,也就是你认为这个变量现在应该是什么样子的,如果变量不是你想象的那样,说明它已经被别人修改过了,你只需要重新读取,设置新期望值,再次尝试修改就好了。

2. CAS 核心源码

java.util.concurrent.atomic 包中的原子类就是通过 CAS 思想来实现,CAS 思想实现靠的是 Unsafe 类。以 AtomicInteger 为例:

【Java 并发编程】CAS 原理解析

Unsafe 类是 CAS 的核心类,由于 Java 方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe 相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe 类存在于 sun.misc 包中,其内部方法操作可以像C的指针一样直接操作内存。

valueOffset 表示当前类对象中使用变量的偏移量,Unsafe 就是根据内存偏移地址获取数据的。

value 要修改的值,用 volatile 修饰,保证了多线程之间的内存可见性。

一起看一下 AtomicInteger.getAndIncrement() 方法是怎么替换内容的:

public final int getAndIncrement() {
	// 拿到当前对象和当前对象的地址,然后加一。
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

// var1 就是当前对象,var2 就是当前对象的内存地址,var4 就是要加的数
public final int getAndAddInt(Object var1, long var2, int var4) {
	// 旧的预期值
    int var5;
    do {
        // 获得当前 var2 地址中的值,可以理解为从当前主物理内存中拷贝一份到自己的本地内存
        var5 = this.getIntVolatile(var1, var2);
    } 
	// 如果var1 = var5,执行var5 + var4,执行成功返回 true,跳出 while 循环,执行失败返回false,自旋
	while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSetInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) {
  oop p = JNIHandles::resolve(obj);
  jint* addr = (jint *)index_oop_from_field_offset_long(p, offset);

  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
} UNSAFE_END

首先获取 var5 旧的预期值,然后调用底层代码 Unsafe_CompareAndSetInt。下面看 Unsafe_CompareAndSetInt 怎么做的:

第一步通过 JNIHandles::resolve() 获取 obj 在内存中 OOP 实例;
第二步根据成员变量 value 反射后计算出的内存偏移值 offset 去内存中取指针 addr;
最后通过 Atomic::cmpxchg(x, addr, e) 实现 CAS。

3. CAS 实现原子操作的三大问题

3.1 ABA 问题

并发环境下,假设初始条件是 A,去修改数据时,发现是 A 就会执行修改。但是看到的虽然是 A,中间可能发生了 A 变 B,B 又变回 A 的情况。此 A 已经非彼 A,数据即使成功修改,也可能有问题。

怎么解决 ABA 问题?

加版本号。

每次修改变量,都在这个变量的版本号上加1,这样发生 A->B->A 时,虽然 A 的值没变,但是它的版本号已经变了,再判断版本号就会发现此时的 A 已经被改过了。参考乐观锁的版本号,这种做法可以给数据带上了一种时效性的检验。

Java 提供了 AtomicStampReference 类,它的 compareAndSet 方法首先检查当前的对象引用值是否等于预期引用,并且当前版本号(Stamp)标志是否等于预期标志,如果全部相等,则以原子方式将引用值和版本号标志的值更新为给定的更新值。

3.2 循环性能开销

自旋 CAS,如果一直循环执行,一直不成功,会给 CPU 带来非常大的执行开销。

怎么解决循环性能开销问题?

在 Java 中,很多使用自旋 CAS 的地方,会有一个自旋次数的限制,超过一定次数,就停止自旋。

3.3 只能保证一个变量的原子操作

CAS 保证的是对一个变量执行操作的原子性,如果对多个变量操作时,CAS 目前无法直接保证操作的原子性的。

怎么解决只能保证一个变量的原子操作问题?

  • 可以考虑改用锁来保证操作的原子性
  • 可以考虑合并多个变量,将多个变量封装成一个对象,从 Java 1.5 开始,JDK 提供了 AtomicReference 类来保证引用对象之间的原子性,就可以把多个变量放在一个对象里来进行 CAS 操作。

4. synchronized、volatile、CAS 比较

(1)synchronized 是悲观锁,属于抢占式,会引起其他线程阻塞。

(2)volatile 提供多线程共享变量可见性和禁止指令重排序优化。

(3)CAS 是基于冲突检测的乐观锁(非阻塞)。文章来源地址https://www.toymoban.com/news/detail-458506.html

到了这里,关于【Java 并发编程】CAS 原理解析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • (学习笔记-进程管理)什么是悲观锁、乐观锁?

    最底层的两种就是 [互斥锁和自旋锁],有很多高级的锁都是基于它们实现的。可以认为它们是各种锁的地基,所以我们必须清楚它们之间的区别和应用。 加锁的目的就是保证共享资源在任意时间内,只有一个线程访问,这样就可以避免多线程导致共享数据错乱的问题。 当已

    2024年02月11日
    浏览(25)
  • 「JUC并发编程」初识CAS锁(概述、底层原理、原子引用、自旋锁、缺点)

    概述 CAS的全称为 Compare-And-Swap ,直译就是 对比交换 。是一条 CPU的原子指令 ,其作用是让CPU 先 进行比较两个值是否相等, 然后 原子地更新某个位置的值。经过调查发现,其实现方式是 基于硬件平台的汇编指令 ,就是说CAS是靠硬件实现的, JVM只是封装了汇编调 用,那些

    2024年02月16日
    浏览(33)
  • java JUC并发编程 第六章 CAS

    第一章 java JUC并发编程 Future: link 第二章 java JUC并发编程 多线程锁: link 第三章 java JUC并发编程 中断机制: link 第四章 java JUC并发编程 java内存模型JMM: link 第五章 java JUC并发编程 volatile与JMM: link 第六章 java JUC并发编程 CAS: link 第七章 java JUC并发编程 原子操作类增强: link 第八章

    2024年02月10日
    浏览(36)
  • Java——并发编程(CAS、Lock和AQS)

    答: Lock 接口比同步方法和同步块提供了 更具扩展性的锁操作 。他们允许更灵活的结构,可以具有完全不同的性质,并且可以支持多个相关类的条件对象。 可以使锁更公平; 可以使线程在等待锁的时候响应中断; 可以让线程尝试获取锁,并在无法获取锁的时候立即返回或

    2024年02月06日
    浏览(38)
  • JavaEE 初阶篇-深入了解 CAS 机制与12种锁的特征(如乐观锁和悲观锁、轻量级锁与重量级锁、自旋锁与挂起等待锁、可重入锁与不可重入锁等等)

    🔥博客主页: 【 小扳_-CSDN博客】 ❤感谢大家点赞👍收藏⭐评论✍ 文章目录         1.0 乐观锁与悲观锁概述         1.1 悲观锁(Pessimistic Locking)         1.2 乐观锁(Optimistic Locking)         1.3 区别与适用场景         2.0 轻量级锁与重量级锁概述         2.1 真正加

    2024年04月16日
    浏览(24)
  • JUC并发编程16 | CAS自旋锁

    是什么,干什么,解决了什么痛点?如何解决,如何使用。 原子类:java.util.concurrent.atomic 在没有CAS之前,多线程环境不使用原子类保证线程安全i++等操作,会出现数据问题,如果直接加锁synchronized,资源的开销就比较大 在出现CAS之后,多线程环境,使用原子类保证线程安全

    2024年02月04日
    浏览(27)
  • JUC并发编程学习笔记(十八)深入理解CAS

    什么是CAS 为什么要学CAS:大厂你必须深入研究底层!有所突破! java层面的cas-------compareAndSet compareAndSet(int expectedValue, int newValue) 期望并更新,达到期望值就更新、否则就不更新! Unsafe类 java不能直接操作内存,但是可以调用c++,c++可以操作内存,java可以通过native定义

    2024年02月05日
    浏览(47)
  • 悲观锁&乐观锁

    1.悲观锁 悲观锁介绍(百科): 悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层

    2024年02月08日
    浏览(25)
  • django实现悲观锁乐观锁

    前期准备 1.原生mysql悲观锁 2.orm实现上述(悲观锁)  3 乐观锁秒杀--》库存还有,有的人就没成功  

    2024年02月12日
    浏览(30)
  • [锁]:乐观锁与悲观锁

    摘要:乐观锁;悲观锁;实现方法;本地锁;分布式锁;死锁;行级锁;表级锁 问题 : ① 在多个线程访问共享资源时,会发生线程安全问题,例如:在根据订单号生成订单时,若用户第一次由于某种原因(网络连接不稳定)请求失败,则会再次发生请求,此时便会产生同一

    2024年02月08日
    浏览(27)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包