带你了解什么是无锁并发 CAS
博主介绍: ✌博主从事应用安全和大数据领域,有8年研发经验,5年面试官经验,Java技术专家,WEB架构师,阿里云专家博主,华为云云享专家✌
💕💕 感兴趣的同学可以收藏关注下 ,不然下次找不到哟💕💕
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)是一种无锁并发算法,它在并发编程中具有一些优点和缺点。
优点:
- 高性能:CAS操作利用硬件提供的原子指令,避免了传统锁机制的线程阻塞和上下文切换的开销,提高了并发性能。
- 避免死锁:由于CAS操作是无锁的,不会出现死锁问题。
- 原子性:CAS操作是原子性的,保证了操作的一致性,不会被其他线程干扰。
缺点:
- ABA问题:CAS操作无法解决ABA问题,即一个值经历了多次变化后又回到原来的值。这可能导致CAS操作在比较时误判。
- 自旋次数过多:如果CAS操作失败,线程需要重试,可能会导致自旋次数过多,浪费CPU资源。
- 只能针对一个变量:CAS操作只能针对一个变量进行比较和交换,无法支持复杂的操作。
3、CAS 的应用场景
CAS(Compare and Swap)在并发编程中有广泛的应用场景,主要用于实现线程安全的操作和数据结构。以下是一些常见的CAS应用场景:
线程安全计数器:CAS可以用于实现线程安全的计数器,如 AtomicInteger 。多个线程可以通过CAS操作对计数器进行原子性的增减操作,避免了传统锁机制的开销。
非阻塞算法:CAS可以用于实现非阻塞算法,如无锁队列、无锁链表等。通过CAS操作,多个线程可以同时对数据结构进行操作,提高并发性能。
乐观锁:CAS可以用于实现乐观锁机制,即在更新数据之前先比较当前值是否与期望值相等,如果相等则进行更新操作。这种机制可以减少锁的使用,提高并发性能。
状态转换:CAS可以用于实现状态转换,如有限状态机。多个线程可以通过CAS操作对状态进行原子性的切换,保证状态的一致性。
并发容器:CAS可以用于实现线程安全的并发容器,如 ConcurrentHashMap 。通过CAS操作,多个线程可以同时对容器进行读写操作,提高并发性能。
4、CAS 的原理
CAS(Compare and Swap)是一种并发编程中的原子操作,用于实现无锁并发算法。CAS操作涉及三个参数:内存地址(或变量)、期望值和新值。其原理如下:
- 首先,读取内存地址中的当前值,作为期望值。
- 接着,比较期望值与内存地址中的当前值是否相等。如果相等,则说明在读取和比较的过程中没有其他线程修改过该值,可以进行下一步操作;如果不相等,则说明有其他线程修改了该值,CAS操作失败,需要重试或进行其他处理。
- 如果期望值与当前值相等,将新值写入内存地址中,完成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
💕💕 本文由激流原创,原创不易,感谢支持
💕💕喜欢的话记得点赞收藏啊
- 点赞
- 收藏
- 关注作者
评论(0)