当前位置: 首页 > news >正文

JUC并发编程_深入理解CAS

JUC并发编程_深入理解CAS

      • CAS 的基本原理
      • CAS 的优缺点
      • Java 中的 CAS 实现
      • 原子引用 AtomicStampedReference 解决 ABA 问题


CAS 的基本原理

CAS 属于乐观锁的一种实现方式

比较(Compare):检查内存位置的值是否等于预期原值。
执行(Swap):如果相等,则将该位置值更新为新值。
原子性:上述两个步骤作为一个整体是原子的,不可中断。


CAS 的优缺点

优点:

  • 非阻塞算法:CAS是一种非阻塞算法,它不会造成线程挂起或阻塞,因此不会引起线程上下文切换,从而减少了系统的开销。

  • 高并发:在多线程环境下,CAS能有效避免使用锁(Locks)带来的问题,如死锁、线程饥饿等,从而提高了系统的并发性能。

缺点:

  • ABA问题:如果某个线程将内存位置的值从A改为B,然后又改回A,此时另一个线程使用CAS进行检查时会认为该值没有变化,但实际上它已经变化过了。

  • 循环时间长开销大:如果CAS操作一直不成功,那么它会一直进行重试,这会增加CPU的开销。

  • 只能保证一个共享变量的原子操作:CAS操作只能保证单个共享变量的原子操作,对于多个共享变量,CAS无法保证其原子性。


Java 中的 CAS 实现

在Java中,CAS主要通过 java.util.concurrent.atomic 包下的类来实现,如 AtomicIntegerAtomicLongAtomicReference 等。这些类提供了 compareAndSet 方法,该方法就是 CAS 操作的 Java 实现。

例如,AtomicInteger 的 compareAndSet 方法签名如下:

public final boolean compareAndSet(int expect, int update)

该方法会尝试将当前值设置为 update,但条件是当前值等于 expect。如果当前值等于 expect,则成功更新并返回 true;否则不更新并返回 false。

import java.util.concurrent.atomic.AtomicInteger;
public class CASDemo {// CAS compareAndSwap: 比较并交换public static void main(String[] args) {AtomicInteger atomicInteger = new AtomicInteger(2020);// public final boolean compareAndSet(int expect, int update)// 如果是期望的值就更新,否则不更新System.out.println(atomicInteger.compareAndSet(2020, 2021));System.out.println(atomicInteger.get());// 通过自旋锁进行自增atomicInteger.getAndIncrement()System.out.println(atomicInteger.compareAndSet(2020, 2021));System.out.println(atomicInteger.get());}
}

getAndIncrement 方法的底层是通过 自旋锁 的方式实现的(循环时间长开销大)

public final int getAndIncrement() {return unsafe.getAndAddInt(this, valueOffset, 1);
}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;}
}

原子引用 AtomicStampedReference 解决 ABA 问题

解决ABA问题的一种常见且有效的方法是使用带有版本号或时间戳的原子操作,这样可以确保在比较并交换(CAS)操作时,不仅能检查值是否相等,还能检查该值自上次读取以来是否未被其他线程修改过。

在 Java 中,java.util.concurrent.atomic 包提供了 AtomicStampedReference 类,它正是为了解决ABA问题而设计的,它在设置值的时候,除了要校验预期原值,还要校验版本号是否变更。

public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp)
import java.util.concurrent.atomic.AtomicStampedReference;  public class ABASolution {  public static void main(String[] args) {// 使用AtomicStampedReference 定义共享变量(初始值1,版本号0)AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(1, 0);  // 尝试将值从1更新为2  int[] expectedStamp = {ref.getStamp()};  int expectedValue = ref.getReference();  boolean resultA = ref.compareAndSet(expectedValue, 2, expectedStamp[0], expectedStamp[0] + 1);  // 尝试将值从1(但实际上是2,因为已经更新了)更新回1  // 这里预期原值和实际值不一致,预期原版本号也与实际版本号不一致,返回失败。boolean resultB1 = ref.compareAndSet(1, 1, expectedStamp[0], expectedStamp[0] + 1); // 正确操作:应重新获取最新的版本号  int currentStamp = ref.getStamp();  boolean resultB2 = ref.compareAndSet(2, 1, currentStamp, currentStamp + 1);}  
}

上述代码需要注意对象引用问题:

  • Integer 使用了对象缓存机制,默认范围为 -128 ~ 127,只有超出这个范围的值才会创建新对象。
  • compareAndSet 比较的是对象是否相同而不是值是否相同。

http://www.mrgr.cn/news/38430.html

相关文章:

  • 如何在Cursor中创建一个RN项目,并部署到Vercel中,可以通过Web访问
  • 赵长鹏今日获释,下一步会做什么?币安透露2024年加密货币牛市的投资策略!
  • 计算机网络自顶向下(1)---网络基础
  • 【HTML5】html5开篇基础(3)
  • 什么是JavaScript 中的类型转换机制,它是如何工作的
  • DarkLabel2.4版本导入MOT17数据集
  • 国庆头像制作小程序相关代码
  • 淘宝扭蛋机小程序:提高扭蛋吸引力
  • 2024网络安全学习路线,最全保姆级教程,学完直接拿捏!
  • FastReport时间格式化(含判空)
  • linux 目录文件夹操作
  • Centos8.5.2111(2)之基于docker容器的SELinux及防火墙配置与管理
  • Spring Boot 3整合FFmpeg进行图片和MP3转换为视频
  • 现代cpp多线程与并发初探
  • 进程的那些事--实现shell
  • 六级翻译 高分笔记
  • 遥感图像变换检测实践上手(TensorRT+UNet)
  • 电子秤PCBA方案应用解决方案设计
  • 系统分析师14:需求工程
  • 如何使用 PyInstaller 将 Python 项目打包成 .exe 文件