并发编程-CAS介绍(结合源码和hostpot说明)

本文详细介绍了CAS(CompareAndSwap)算法的运行原理,包括其工作流程和自旋锁的应用。同时探讨了CAS的局限性,如ABA问题和自旋时间过长,提供了相应的解决方案,如使用AtomicStampedReference。
摘要由CSDN通过智能技术生成

目录

一、什么是CAS

二、CAS的运行原理

2.1 运行过程

2.2 自旋锁

三、CAS底层实现分析

3.1 代码例子

3.2 测试结果

3.3 分析代码

3.3.1 java 层面

3.3.2 hotspot 层面

四、CAS的缺点

4.1 局限性

4.2 ABA问题

4.2.1 问题描述

4.2.2 解决方案

4.2.2.1 代码示例

4.2.2.2 测试结果

4.3 自旋时间过长问题

4.3.1 问题说明

4.3.2 解决方案


一、什么是CAS

CAS全称 Compare And Swap(比较与交换),是一种无锁算法,是线程并发运行时用到的一种技术。在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。

二、CAS的运行原理

2.1 运行过程

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

  • 需要读写的内存值 V;
  • 进行比较的值 A;
  • 要写入的新值 B;

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

2.2 自旋锁

自旋锁的实现基础是CAS算法机制。CAS自旋锁属于乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去尝试更新。

说到这里,不得不解析下自旋锁的概念:

是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。

自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。

对于互斥锁,会让没有得到锁资源的线程进入BLOCKED状态,而后在争夺到锁资源后恢复为RUNNABLE状态,这个过程中涉及到操作系统用户模式和内核模式的转换,代价比较高。但是自旋锁不会引起调用者堵塞,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁。

三、CAS底层实现分析

3.1 代码例子

使用atomic下的原子类AtomicInteger:

package com.ningzhaosheng.thread.concurrency.features.atom.cas;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * @author ningzhaosheng
 * @date 2024/2/5 18:37:23
 * @description 测试CAS
 */
public class TestCas {
    private static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {

        test1();
    }

    public static void test1() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                count.incrementAndGet();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                count.incrementAndGet();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }

}

3.2 测试结果

从结果中我们可以看出,执行结果符合预期值,从而得出:使用CAS基础上提供的原子类AtomicInteger,从而解决并发安全问题。

3.3 分析代码

3.3.1 java 层面

我们可以分析下AtomicInteger源码,从上图中可以看到,AtomicInteger在初始化时,会初始化一个Unsafe类,然后通过这个Unsafe类调用objectFieldOffset方法获取到AtomicInteger的初始值。其实就是我们在new AtomicInteger时初始化的值,这里是0。

我们接着分析,通过上图,我们可以知道,我们调用AtomicInteger的incrementAndGet()方法,然后接着调用Unsafe的getAndAddInt()方法,getAndAddInt()这个方法,最终调用的是JDK 提供的native方法compareAndSwapInt(),这个方法就是基于CAS锁的实现,最终JVM会帮助我们将方法实现CAS汇编指令。

3.3.2 hotspot 层面

  • 首先我们打开openjdk官网

OpenJDK Mercurial Repositories

  • 然后找到unsafe.cpp类

jdk8u/jdk8u/hotspot: 69087d08d473 src/share/vm/prims/unsafe.cpp

通过查看hostpot 源码我们可以知道,Java中Unsafe类中的native 方法:compareAndSwapInt(),最终调用到C++层面的Unsafe_CompareAndSwapInt方法,该方法最终调用了一条CPU并发原语:cmpxchg指令。它是一个原子操作,通过这个并发指令实现了共享变量的并发访问安全。

四、CAS的缺点

4.1 局限性

CAS只能保证对一个变量的操作是原子性的,无法实现对多行代码实现原子性。

4.2 ABA问题

4.2.1 问题描述

比如A、B两个线程,同时对共享变量num=5进行操作,A线程操作需要花费5s,B线程需要10s,A线程先花5s时间将共享变量num修改为了6,然后再花5s将共享变量num修改回了5,此时10s的时候,B线程将共享变量修改为10,由于它之前拿到的初始值还是5,符合CAS比较交换的定义,所以能修改成功,但是其实这个值已经被其他线程修改了两次,对于这种变化,A线程是不知道的,这就是CAS的ABA问题。

4.2.2 解决方案

解决方案:数值追加版本号,使用AtomicStampedReference,在CAS时,不但会判断原值,还会比较版本信息。

4.2.2.1 代码示例
package com.ningzhaosheng.thread.concurrency.features.atom.cas;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * @author ningzhaosheng
 * @date 2024/2/5 18:37:23
 * @description 测试CAS
 */
public class TestCas {
    private static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {

        test_atomic_stamped();
    }


    public static void test_atomic_stamped() {
        AtomicStampedReference<String> reference = new AtomicStampedReference<>("AAA", 1);

        String oldValue = reference.getReference();
        int oldVersion = reference.getStamp();

        boolean b = reference.compareAndSet(oldValue, "B", oldVersion, oldVersion + 1);
        System.out.println("修改1版本的:" + b);

        boolean c = reference.compareAndSet("B", "C", 1, 1 + 1);
        System.out.println("修改2版本的:" + c);
    }
}

4.2.2.2 测试结果

4.3 自旋时间过长问题

4.3.1 问题说明

在CAS比较交换的时候,如果修改值不成功,会一直循环尝试修改,直到成功为止,这个循环就称为自旋,而循环的时间就叫自旋时间。在高并发场景下,使用CAS方式修改共享变量会有自旋时间过长的问题,而自旋本身消耗性能。

4.3.2 解决方案

可以指定CAS一共循环多少次,如果超过这个次数,直接失败/或者挂起线程。(自旋锁、自适应自旋锁)

可以在CAS一次失败后,将这个操作暂存起来,后面需要获取结果时,将暂存的操作全部执行,再返回最后的结果。

好了,本次内容就分享到这,欢迎关注本博主。如果有帮助到大家,欢迎大家点赞+关注+收藏,有疑问也欢迎大家评论留言!

  • 30
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值