首页 > 编程语言 >java并发之CAS(Compare and swap)

java并发之CAS(Compare and swap)

时间:2023-06-13 22:25:30浏览次数:45  
标签:Compare java CAS value int 内存 oldValue public

1. 简介

CAS的底层调用native方法,最终是利用CPU的一个特殊指令,该指令由CPU保证了原子性,而且包含多个操作,比如先比较再更新。

原理:

  • (1)需要读写的内存值(V)、原值(A)和新值(B)。如果V的值与原值A相匹配,那么把B设置给V,否则处理器不做任何操作。
  • (2)无论哪种情况,都返回V内存值。
  • (3)原子类里,当失败时,就一直循环,直到成功。

(1)、(2)是在CPU和内存的层面来说的,(3)是在Java层面说的

比如 AtomicInteger中的源码中,就使用到了 CAS:

public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

2. CAS的等价代码

自己实现CAS:

/**
  *      模拟CAS操作,等价代码
 */
public class SimulatedCAS {
    private int value;

    /**
     * 模拟CAS操作
     * @param expectedValue 预期值
     * @param newValue 修改值
     * @return 原值
     */
    public synchronized int compareAndSwap(int expectedValue, int newValue) {
        int oldValue = value;

        if (oldValue == expectedValue) {
            value = newValue;
        }
        return oldValue;
    }
}

在多线程中调用上面自己实现的CAS:

/**
  *      两个线程竞争,其中一个修改落败
 */
public class TwoThreadCompetition implements Runnable{
    private int value;

    /**
     * 模拟CAS操作
     * @param expectedValue 预期值
     * @param newValue 修改值
     * @return 旧值
     */
    public synchronized int compareAndSwap(int expectedValue, int newValue) {
        int oldValue = value;

        if (oldValue == expectedValue) {
            value = newValue;
            System.out.println(Thread.currentThread().getName()+"将值从"+oldValue+"修改成"+newValue);

        }
        return oldValue;
    }

    //主函数
    public static void main(String[] args) throws InterruptedException {
        TwoThreadCompetition r = new TwoThreadCompetition();
        r.value=0;//默认值
        Thread thread1 = new Thread(r,"线程1");
        thread1.start();
        thread1.join();
        System.out.println(r.value);
        Thread thread2 = new Thread(r,"线程2");
        thread2.start();
        thread2.join();
        System.out.println(r.value);
    }

    @Override
    public void run() {
        compareAndSwap(0,1);
    }
}

image-20230611211632613

打印结果显示,只有第一个线程对 value 进行了修改,第二个线程没有修改,符合CAS的逻辑。

3. 应用场景

(1)乐观锁

乐观锁的实现就是利用CAS原理,比如数据库在修改时,是利用version字段来判断是否应该执行修改,而不是加锁。

(2)并发容器

如ConcurrentHashMap类中的put()方法

(4)原子类

这里以 AtomicInteger 的 getAndIncrement()方法 为例剖析如何利用CAS实现原子性的

  • AtomicInteger加载Unsafe工具,用来直接操作内存数据
  • 用Unsafe工具来实现底层操作
  • 用volatile修饰value字段,保证可见性
上面使用到了 Unsafe 类,那么他是个什么呢?
  • Unsafe是CAS的核心类。Java一般无法直接访问底层操作系统,而是通过本地(native)方法来访问。不过尽管如此,JVM还是开了一个后门,JDK中有一个类Unsafe,它提供了硬件级别的原子操作。
  • valueOffset表示的是变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的原值的,这样我们就能通过unsafe来实现CAS了
总结:

总之,getAndIncrement() 的实现原理是:先获取 value 在内存中的地址,然后使用该地址拿到原值,再调用 compareAndSwapInt 方法,将内存地址value,原值,和新值传进去,执行 CAS 方法,compareAndSwapInt 是 native 方法,由c++实现,如下

上面的 C++ 代码可以看出:方法中先想办法拿到变量value在内存中的地址;通过Atomic::cmpxchg实现原子性的比较和替换,其中参数x是即将更新的值,参数e是原内存的值。至此,最终完成了CAS的全过程。

4 CAS的缺点

(1)ABA 问题

什么是ABA?

假若一个变量初次读取是A,在compare阶段依然是A,但其实可能从读取A到后面的compare这段过程里,它先被改为B,再被改回A,而CAS是无法意识到这个问题的。CAS只关注了比较那一刻的值是否A,而无法清楚在此过程中变量的变更明细,这就是所谓的ABA漏洞。

如何解决ABA问题

可以采用数据库的乐观锁方式,采用version版本号,每次修改就给version版本号+1

(2)自旋时间过长

do-while的自旋循环,如果多线程竞争激烈的情况下,锁一直拿不到,他就会一直自旋,消耗CPU性能。

点我扫码关注微信公众号

文章来源:Java并发之CAS(Compare and swap)


个人微信:CaiBaoDeCai

微信公众号名称:Java知者

微信公众号 ID: JavaZhiZhe

谢谢关注!

标签:Compare,java,CAS,value,int,内存,oldValue,public
From: https://www.cnblogs.com/javazhizhe/p/17478838.html

相关文章

  • Java并发之 Lock 锁
    一、Lock接口1Lock简介&地位&作用锁是一种工具,用于控制对共享资源的访问Lock和synchronized是最常见的两个锁,他们都能够达到线程安全的目录,但是使用和功能上又有较大的不同Lock接口最常见的实现类就是ReentrantLock通常情况下Lock只允许一个线程访问共享资源,特殊情况也允......
  • Java并发之原子类
    一、原子类简介1什么是原子类Java中提供了一些原子类,原子类包装了一个变量,并且提供了一系列对变量进行原子性操作的方法。原子性的意思是对于一组操作,要么全部执行成功,要么全部执行失败,不能只有其中某几个执行成功。在多线程的情况下能够保证操作不会被中断,从而能保证并发安......
  • Java并发工具之ThreadLocal
    一、ThreadLocal简介1.ThreadLocal是什么?ThreadLocal字面意思是本地线程,其实更准确来说是线程局部变量,线程类Thread有个变量叫做threadLocals,其类型就是ThreadLocal.ThreadLocalMap类型,他其实不是一个Map类型,但可以暂时理解它是一个Map,键为ThreadLocal对象,值就是要......
  • Java中Lambda表达式
    Demo1:packagecom.itheima.d9_lambda;publicclassLambdaDemo1{publicstaticvoidmain(String[]args){//目标:学会使用lambda的标准格式简化匿名内部类的代码形式Animala=newAnimal(){@Overridepublicvoidrun(......
  • Java反序列化Commons-Collection篇06-CC5链
    <1>环境分析jdk:jdk8u65CC:Commons-Collections3.2.1pom.xml添加<dependencies><dependency><groupId>commons-collections</groupId><artifactId>commons-collections</artifactId>......
  • Java课程设计--象棋--齐鲁工业大学
    目录1.项目简介2.项目采用技术 3.功能需求分析4.项目亮点5.项目功能架构图和UML类图6.主要功能截图7.团队成员负责模块(表格形式)8.项目git地址9.团队成员git提交截图10.项目总结 正文1.项目简介主要研究基于JAVA技术的中国象棋游戏的分析与设计,对中国象棋游......
  • Java基本查找,二分查找,选择排序
    一、基本查找packagecom.itheima.d8_sort_binarysearch;/***基本查找*/importjava.util.Scanner;publicclassTest3{publicstaticvoidmain(String[]args){//1、定义一个数组(基本查找)int[]arr={12,95,1,3,76,4,2,93,56,49,67};......
  • Java面试笔记202306
    Java基础ArrayListArrayList底层数据是动态数组,初始长度为10,每次扩容为原来的1.5倍。扩容流程:首先会创建一个新的长度的数组,然后使用Arrays.copyOf()方法将旧的数组中的元素复制到新的数组中,最后会将新插入的数据插入到新的数组中。IO和NIO的区别io指的是io流。可以实现数......
  • Java反序列化之Commons-Collection篇05-CC2链
    <1>环境分析jdk:jdk8u65CC:Commons-Collections4.0pom.xml添加<dependency><groupId>org.apache.commons</groupId><artifactId>commons-collections4</artifactId><version>4.0</version></dependency&g......
  • javascript:eval()的用法
    eval()是JavaScript中的一个全局函数,它可以计算或执行参数。如果参数是表达式,则eval()计算表达式;如果参数是一个或多个JavaScript语句,则eval()执行这些语句。以下是一些常见的用法:-计算表达式的值:`varresult=eval("3+5");`-执行JavaScript代码字符串:`varcode......