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

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

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

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

1688205710201.jpg

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操作涉及三个参数:内存地址(或变量)、期望值和新值。其原理如下:

  6. 首先,读取内存地址中的当前值,作为期望值。

  7. 接着,比较期望值与内存地址中的当前值是否相等。如果相等,则说明在读取和比较的过程中没有其他线程修改过该值,可以进行下一步操作;如果不相等,则说明有其他线程修改了该值,CAS操作失败,需要重试或进行其他处理。
  8. 如果期望值与当前值相等,将新值写入内存地址中,完成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

1686494501743.jpg

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

目录
相关文章
|
4月前
多线程并发锁的方案—原子操作
多线程并发锁的方案—原子操作
|
4月前
|
应用服务中间件 Linux 调度
锁和原子操作CAS的底层实现
锁和原子操作CAS的底层实现
19 0
|
6月前
|
存储 编译器 API
锁与原子操作CAS
锁与原子操作CAS
91 0
|
4月前
|
存储 安全 中间件
锁与原子操作CAS的底层实现
锁与原子操作CAS的底层实现
|
4月前
多线程并发锁方案—自旋锁
多线程并发锁方案—自旋锁
|
4月前
|
缓存 Linux API
原子操作CAS与锁实现
原子操作CAS与锁实现
|
4月前
多线程并发锁的方案—互斥锁
多线程并发锁的方案—互斥锁
|
9月前
|
Java 编译器 Linux
【多线程】锁策略、CAS、Synchronized
锁策略, cas 和 synchronized 优化过程
|
4月前
|
存储 缓存 算法
理解原子操作与CAS锁
理解原子操作与CAS锁
28 0
|
4月前
|
存储 消息中间件 算法
无锁CAS_无所队列
无锁CAS_无所队列
32 0