带你了解什么是无锁并发 CAS

举报
激流丶 发表于 2023/07/01 18:11:05 2023/07/01
【摘要】 带你了解什么是无锁并发 CAS

博主介绍: ✌博主从事应用安全和大数据领域,有8年研发经验,5年面试官经验,Java技术专家,WEB架构师,阿里云专家博主,华为云云享专家✌

💕💕 感兴趣的同学可以收藏关注下不然下次找不到哟💕💕

image.png

1、什么是 CAS

在Java中,CAS(Compare and Swap)是一种并发编程中常用的技术。它是通过原子操作来实现无锁并发的一种机制。在Java中,CAS通常使用 java.util.concurrent.atomic 包中的原子类来实现。

CAS操作包含三个参数:内存地址、期望值和新值。它会比较内存地址中的值与期望值,如果相等,则将内存地址中的值替换为新值。整个操作是原子性的,不会被其他线程干扰。

在Java中,常用的CAS操作是使用 AtomicInteger 、 AtomicLong 、 AtomicReference 等原子类。这些类提供了一系列的原子操作方法,如 compareAndSet() 用于比较和设置值,以实现线程安全的操作。

CAS操作在并发编程中具有重要的作用,可以避免传统锁机制所带来的线程阻塞和上下文切换的开销,提高了并发性能。然而,CAS也存在一些限制和问题,例如ABA问题和自旋次数过多的风险,需要开发者注意和处理。

2、CAS 的优缺点

CAS(Compare and Swap)是一种无锁并发算法,它在并发编程中具有一些优点和缺点。

优点:

  1. 高性能:CAS操作利用硬件提供的原子指令,避免了传统锁机制的线程阻塞和上下文切换的开销,提高了并发性能。
  2. 避免死锁:由于CAS操作是无锁的,不会出现死锁问题。
  3. 原子性:CAS操作是原子性的,保证了操作的一致性,不会被其他线程干扰。

缺点:

  1. ABA问题:CAS操作无法解决ABA问题,即一个值经历了多次变化后又回到原来的值。这可能导致CAS操作在比较时误判。
  2. 自旋次数过多:如果CAS操作失败,线程需要重试,可能会导致自旋次数过多,浪费CPU资源。
  3. 只能针对一个变量:CAS操作只能针对一个变量进行比较和交换,无法支持复杂的操作。

3、CAS 的应用场景

CAS(Compare and Swap)在并发编程中有广泛的应用场景,主要用于实现线程安全的操作和数据结构。以下是一些常见的CAS应用场景:

  1. 线程安全计数器:CAS可以用于实现线程安全的计数器,如 AtomicInteger 。多个线程可以通过CAS操作对计数器进行原子性的增减操作,避免了传统锁机制的开销。

  2. 非阻塞算法:CAS可以用于实现非阻塞算法,如无锁队列、无锁链表等。通过CAS操作,多个线程可以同时对数据结构进行操作,提高并发性能。

  3. 乐观锁:CAS可以用于实现乐观锁机制,即在更新数据之前先比较当前值是否与期望值相等,如果相等则进行更新操作。这种机制可以减少锁的使用,提高并发性能。

  4. 状态转换:CAS可以用于实现状态转换,如有限状态机。多个线程可以通过CAS操作对状态进行原子性的切换,保证状态的一致性。

  5. 并发容器:CAS可以用于实现线程安全的并发容器,如 ConcurrentHashMap 。通过CAS操作,多个线程可以同时对容器进行读写操作,提高并发性能。

4、CAS 的原理

CAS(Compare and Swap)是一种并发编程中的原子操作,用于实现无锁并发算法。CAS操作涉及三个参数:内存地址(或变量)、期望值和新值。其原理如下:

  1. 首先,读取内存地址中的当前值,作为期望值。
  2. 接着,比较期望值与内存地址中的当前值是否相等。如果相等,则说明在读取和比较的过程中没有其他线程修改过该值,可以进行下一步操作;如果不相等,则说明有其他线程修改了该值,CAS操作失败,需要重试或进行其他处理。
  3. 如果期望值与当前值相等,将新值写入内存地址中,完成CAS操作。如果写入成功,则CAS操作成功;如果写入失败(可能由于其他线程同时修改了该值),需要重试或进行其他处理。

CAS操作的关键在于利用硬件提供的原子指令,实现对内存地址的读取、比较和写入的原子性操作。通过不断重试,CAS操作可以保证只有一个线程能够成功修改内存地址中的值,从而实现线程安全的并发操作。

需要注意的是,CAS操作可能存在ABA问题,即一个值经历了多次变化后又回到原来的值。为了解决ABA问题,可以使用版本号或标记位等方式对值进行扩展,使得CAS操作能够检测到值的变化历史。

5、什么是 ABA 问题?(面试必问)

ABA问题是一种在并发编程中可能出现的问题,它涉及到共享变量的值经历了多次变化后又回到原来的值,从而导致CAS操作可能出现误判的情况。

具体来说,假设有两个线程A和B同时对一个共享变量进行操作。开始时,共享变量的值为A。线程A首先将该变量的值从A修改为B,然后又将其修改回A。与此同时,线程B将其修改为C。在这个过程中,线程B可能无法察觉到线程A的第二次修改,因为最终的值与线程B期望的值相同,导致CAS操作成功。

ABA问题可能会导致一些潜在的问题,例如在使用CAS实现的无锁数据结构中,可能会发生数据不一致的情况。为了解决ABA问题,可以使用一些手段来增加值的标识,使得CAS操作能够检测到值的变化历史。

一种常见的解决方案是引入版本号或标记位。每次对共享变量进行修改时,都会增加一个递增的版本号。这样,在进行CAS操作时,除了比较值是否相等,还需要比较版本号是否一致。如果版本号不一致,说明共享变量的值已经发生了变化,CAS操作会失败。

另一种解决方案是使用带有时间戳的变量。每次对共享变量进行修改时,都会记录当前的时间戳。在进行CAS操作时,除了比较值是否相等,还需要比较时间戳是否一致。如果时间戳不一致,说明共享变量的值已经发生了变化,CAS操作会失败。

通过引入版本号、标记位或时间戳等机制,可以解决ABA问题,确保CAS操作的准确性和可靠性。

6、CAS 的代码案例

代码案例如下:

package com.pany.camp.cas;

import java.util.concurrent.atomic.AtomicInteger;

/**
 *
 * @description:  CAS
 * @copyright: @Copyright (c) 2022
 * @company: Aiocloud
 * @author: pany
 * @version: 1.0.0
 * @createTime: 2023-07-01 17:58
 */
public class CASExample {
    private static AtomicInteger sharedVariable = new AtomicInteger(0);

    public static void main(String[] args) {
        // 创建两个线程,分别进行CAS操作
        Thread thread1 = new Thread(() -> {
            int expectedValue = sharedVariable.get(); // 获取共享变量的当前值
            int newValue = expectedValue + 1; // 修改共享变量的值
            boolean success = sharedVariable.compareAndSet(expectedValue, newValue); // 使用CAS操作进行原子性更新
            if (success) {
                System.out.println("Thread 1: CAS operation successful");
            } else {
                System.out.println("Thread 1: CAS operation failed");
            }
        });

        Thread thread2 = new Thread(() -> {
            int expectedValue = sharedVariable.get(); // 获取共享变量的当前值
            int newValue = expectedValue + 1; // 修改共享变量的值
            boolean success = sharedVariable.compareAndSet(expectedValue, newValue); // 使用CAS操作进行原子性更新
            if (success) {
                System.out.println("Thread 2: CAS operation successful");
            } else {
                System.out.println("Thread 2: CAS operation failed");
            }
        });

        // 启动线程
        thread1.start();
        thread2.start();

        try {
            // 等待线程执行完毕
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 输出最终的共享变量值
        System.out.println("Final value of shared variable: " + sharedVariable.get());
    }
}

输出如下:

Thread 2: CAS operation successful
Thread 1: CAS operation successful
Final value of shared variable: 2

Process finished with exit code 0

image.png

💕💕 本文由激流原创,原创不易,感谢支持
💕💕喜欢的话记得点赞收藏啊

image.png

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。