【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

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

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • matlab如何取模_matlab取模运算

    matlab如何取模_matlab取模运算mod函数采用floor,rem函数采用fix函数。那么什么是floor和fix?fix(x):截尾取整。如:>>fix([3.4,-3.4])ans=3-3floor

  • java如何将字符串转化为日期_java如何将字符串转为日期「建议收藏」

    java如何将字符串转化为日期_java如何将字符串转为日期「建议收藏」一、概述将Java中字符串“2010年1月2日”格式的转换为日期的最佳方法是什么?最终,我想将月份,日期和年份分解为整数,以便可以使用Datedate=newDate();date.setMonth()..date.setYear()..date.setDay()..date.setlongcurrentTime=date.getTime();将日期转换为时间。二、详解java.u…

    2022年10月31日
  • java中jersey什么意思_java jersey介绍

    java中jersey什么意思_java jersey介绍简介Jersey是JAX-RS(JSR311)开源参考实现用于构建RESTfulWebservice,它包含三个部分:核心服务器(CoreServer):通过提供JSR311中标准化的注释和API标准化,可以用直观的方式开发RESTfulWeb服务。核心客户端(CoreClient):Jersey客户端API能够帮助开发者与RESTful服务轻松通信;集成(Integration):J…

  • java基础之java输入输出语句[通俗易懂]

    java基础之java输入输出语句[通俗易懂]1、使用Scanner使用时需要引入包importjava.util.Scanner;首先定义Scanner对象Java基础学习:java输入输出语句比如:Java基础学习:java输入输出语句2、使用BufferedReader用前需要引入importjava.io.Reader;Java基础学习:java输入输出语句比如:Java基础学习:java输入输出语句应该注意的是:Java把从键盘输入的数据一律看作是字符串,因此若要从键盘输入并让系统认可是数值型数据,必须经过转换。

  • python编写怎么换行_python表示换行

    广告关闭腾讯云11.11云上盛惠,精选热门产品助力上云,云服务器首年88元起,买的越多返的越多,最高返5000元!windows换行符是’rn’,unixlinux的换行符为’n’,mac的换行符为’r’,在python中,对换行符进行了统一处理,定义为’n。到此这篇关于python代码中怎么换行的文章就介绍到这了,更多相关python写代码怎么换行内容请搜索zalou.cn以前的文章或继续浏…

  • MySQL binlog日志格式 binlog_format[通俗易懂]

    MySQL binlog日志格式 binlog_format[通俗易懂]MySQLbinlog日志格式binlog_formatMySQL5.5中对于二进制日志(binlog)有3种不同的格式可选:Mixed,Statement,Row,默认格式是Statement。总结一下这三种格式日志的优缺点。MySQLReplication复制可以是基于一条语句(StatementLevel),也可以是基于一条记录(RowLevel),可以…

发表回复

您的电子邮箱地址不会被公开。

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