【CAS】CAS原理「建议收藏」

【CAS】CAS原理「建议收藏」CAS是所有原子类的底层原理,乐观锁主要采用CAS算法。CAS,比较并交换,是==JDK提供的非阻塞原子性操作,通过硬件保证比较-更新操作的原子性。==通常结合volatile保证共享变量的原子性。

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用

1 CAS原理

CAS是所有原子类的底层原理,乐观锁主要采用CAS算法。

CAS,比较并交换,是JDK提供的非阻塞原子性操作,通过硬件保证比较-更新操作的原子性。 通常结合volatile保证共享变量的原子性。

思想:获取当前变量最新值A(预期值),然后进行CAS操作。此时如果内存中变量的值V(内存值V)等于预期值A,说明没有被其他线程修改过,我就把变量值改成B(更新值);如果不是A,便不再修改。

CAS操作利用CPU的特殊指令,由CPU保证原子性,完成一系列操作,不存在安全性问题。

CAS的变量需要用volatile修饰,以便在各线程之间保证可见。

CAS算法思想的使用场景

  • 乐观锁
  • 并发容器,例如ConcurrentHashMap
  • 原子类

2 AtomicLong中CAS使用分析

// 获取Unsafe实例
private static final Unsafe unsafe = Unsafe.getUnsafe();
// 获取变量value在内存中的偏移量
private static final long valueOffset;
static { 
   
    try { 
   
        valueOffset = unsafe.objectFieldOffset
            (AtomicLong.class.getDeclaredField("value"));
    } catch (Exception ex) { 
    throw new Error(ex); }
}

类加载过程中首先会加载Unsafe实例和valueOffset偏移量。valueOffset 表示变量在内存中的偏移地址,Unsafe根据内存偏移地址获取数据的预期值,然后进行CAS操作。为保证获取到值是最新值,因此变量通常用volatile修饰。

public final long getAndIncrement() { 
   
    return unsafe.getAndAddLong(this, valueOffset, 1L);
}
// Unsafe类
public final long getAndAddLong(Object var1, long var2, long var4) { 
   
    long var6;
    do { 
   
        // 通过对象地址和偏移量获取变量的最新值
        var6 = this.getLongVolatile(var1, var2);
        // 满足条件进行CAS操作
    } while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));

    return var6;
}

CAS方法底层c++源码实现

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapLong(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jlong e, jlong x))
  UnsafeWrapper("Unsafe_CompareAndSwapLong");
  Handle p (THREAD, JNIHandles::resolve(obj));
  // 内存地址
  jlong* addr = (jlong*)(index_oop_from_field_offset_long(p(), offset));
  if (VM_Version::supports_cx8())
    // Atomic::cmpxchg原子性比较和替换
    return (jlong)(Atomic::cmpxchg(x, addr, e)) == e;
  else {
    jboolean success = false;
    ObjectLocker ol(p, THREAD);
    if (*addr == e) { *addr = x; success = true; }
    return success;
  }
UNSAFE_END

3 CAS的缺点

3.1 ABA问题

CAS是比较值,如果值相等则变换。此处可能存在这样的情况,线程1获取变量值为5,线程2将值改为10,线程3再将值改回5。对于线程1,变量的值没有变,但对于计数等后续操作是不正确的。

分析:ABA问题的产生是因为变量的状态值产生了环形变换。如果变量的值只能朝一个方向转化,便不会构成环形,不存在ABA问题。

解决方法:可以参考数据库乐观锁的处理,加版本号,变量更新时版本号会改变。通过比较版本号代替比较变量值。与集合的Fast-Fail机制类似,检查modCount值是否一致。

3.2 自旋时间长带来性能消耗

以AtomicLong为例,高并发场景下,如果线程一直无法进行CAS操作,内部是dowhile死循环,会一直自旋,消耗CPU。

自旋的理解

自旋是重试策略,既可以是乐观锁的重试策略,也可以使悲观锁的重试策略。例如AtimicLong很多方法都调用了getAndAddLong方法,内部便是利用了自旋+CAS操作。至于悲观锁,ReentrantLock的超时获取锁方法tryLock,便利用了for循环。

4 Unsafe

Unsafe是CAS核心类。Java无法直接访问底层操作系统,而是通过本地方法访问。JDK中Unsafe类,底层调用本地方法,提供硬件级别原子操作。

Unsafe类属于rt.jar包,使用bootstrap类加载器加载,而普通main函数类是使用AppClassLoader加载。Unsafe直接操作内存,因此叫不安全类,不能随意调用。

可以通过反射获取Unsafe实例。

Field field = Unsafe.class.getDeclareField("theUnsafe");
field.setAccessible(true);

本文案例代码位置:https://gitee.com/dtyytop/advanced-java

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/181293.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • C语言-判断回文字符串(二)

    C语言-判断回文字符串(二)7-2 判断回文字符串 (15分)输入一个字符串,判断该字符串是否为回文。回文就是字符串中心对称,从左向右读和从右向左读的内容是一样的。输入格式:输入在一行中给出一个不超过80个字符长度的、以回车结束的非空字符串。输出格式:输出在第1行中输出字符串。如果它是回文字符串,在第2行中输出Yes,否则输出No。输入样例1:level输出样例1:levelYe…

    2022年5月30日
    48
  • node.js 安装详细步骤教程

    node.js 安装详细步骤教程 本机环境:Windows10专业版x64 1、下载安装包Node.js官方网站下载:https://nodejs.org/en/选择操作系统对应的包:下载完成,安装包如下: 2、安装打开安装,傻瓜式下一步即可:   选择安装位置,我这里装在D盘下:     安装成功,文件夹结构…

    2022年7月16日
    14
  • 插入排序

    插入排序

    2021年12月14日
    55
  • 控制台打印图形_前端控制台打印

    控制台打印图形_前端控制台打印问题描述一、在控制台输出以星号打印的三角形思路:在外部使用循环语句执行5次每次打印1行,每行的内容分别为空格和星号,每行空格缩进的数量为5减去所在行数,星号的数量是所在行数的2倍减1。在内部使用循环语句首先打印空格,然后打印星号”*”,对应的打印次数用循环次数控制,打印星号之后就可以换行。publicstaticvoidmain(String[]args){ //打印图形, intn=5;//表示要打印几行 for(inti=1;i<=n;i++){//i表示每行

    2022年10月20日
    3
  • 笛卡尔积简单介绍「建议收藏」

    笛卡尔积简单介绍「建议收藏」title:简单介绍笛卡尔积date:2020-10-1320:26:18tags:“离散数学????”categories:“2020/10”简介这个博客参考左孝陵先生的《离散数学》,会尽量简单的讲讲笛卡尔积,能够给大家一个更加具体的认识。什么是序偶要知道什么是序偶,先得弄明白序偶的作用,我觉得序偶就是带顺序的集合,用来表示一些集合表示不了的东西。比如在小学学的直角坐标系上,有两个点,(2,3)和(3,2),点的坐标就是序偶,因为它自带顺序,为什么每次一个点都先读x坐标再读y坐标

    2022年7月11日
    22
  • 【数据库】模糊查询[通俗易懂]

    【数据库】模糊查询[通俗易懂]【数据库】模糊查询

    2022年4月25日
    128

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号