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

高性能锁机制 CAS:Java 并发编程中的深度剖析

引言

在并发编程领域,i++操作的非线程安全性是开发者们熟知的问题。这一现象根源在于i++并非原子操作,其内部执行过程包含读取、修改和写入三个步骤,在多线程环境下极易因线程切换导致数据竞争与不一致,这与我们此前探讨的多线程常见问题紧密相关。

为保障操作的原子性,加锁机制成为常见解决方案。在 Java 技术栈中,synchronized关键字与 CAS(Compare And Swap,比较与交换)机制是实现线程同步的两大核心手段。但二者在设计理念与执行逻辑上存在显著差异,分别对应悲观锁与乐观锁两种截然不同的并发控制策略。

悲观锁:以synchronized为代表的保守策略

synchronized作为悲观锁的典型代表,自 JDK 早期版本便已存在。尽管随着 JDK 的迭代优化,其性能表现大幅提升,从重量级锁逐步演变为轻量级锁与偏向锁,但本质上仍属于悲观锁范畴。

悲观锁的设计哲学在于对冲突的高度警惕,它默认每次访问共享资源时都可能发生竞争,因此强制要求线程在进入临界区前必须获取锁。以i++操作为例,当一个线程通过synchronized锁定目标变量后,其他线程若试图访问该变量,将立即被阻塞并进入等待队列,直至持有锁的线程执行完毕并释放锁资源。这一过程可类比为日常生活场景:当一人进入卫生间后立即反锁房门(获取锁),在其使用完毕并开门(释放锁)前,后续使用者只能在外等候(线程阻塞),即便需求迫切也无法强行进入。这种策略虽能确保数据安全,但频繁的锁获取与释放操作,以及线程阻塞带来的上下文切换开销,在高并发场景下可能成为性能瓶颈。

乐观锁:基于 CAS 的积极尝试

与悲观锁形成鲜明对比,CAS 机制作为乐观锁的核心实现,展现出截然不同的并发控制思路。乐观锁假定在大多数情况下,线程对共享资源的访问不会产生冲突,因此线程执行时无需预先加锁,而是直接尝试完成操作。

具体到i++场景,线程首先读取变量的当前值作为预期值,随后在修改并写入新值前,通过 CAS 操作比较内存中的实际值是否与预期值一致。若一致,则原子性地更新变量;若不一致,说明在此期间有其他线程已修改该变量,当前线程将重新读取最新值并再次尝试,直至操作成功。这一过程类似于多人协作填写共享表格,每个人在提交修改前会检查内容是否被他人改动,若未变动则提交,否则重新获取最新版本进行修改。由于乐观锁避免了线程阻塞,显著减少了上下文切换开销,尤其适用于读多写少的场景。此外,因其无需显式加锁与解锁,从原理上杜绝了死锁产生的可能性。

接下来我们来有条理且全面的理解一下CAS。

一、CAS 核心原理

CAS 的核心思想基于比较 - 验证 - 更新的原子性操作流程,其核心目标是在多线程环境下实现共享变量的原子更新。CAS 操作涉及三个关键操作数:

  1. V(Var):待更新的共享变量
  1. E(Expected):预期值,即操作前读取到的变量值
  1. N(New):新值,用于替换 V 的目标值

当且仅当内存中的变量 V 当前值等于预期值 E 时,CAS 操作才会将 V 原子性地更新为新值 N;若 V 的值已被其他线程修改(即 V != E),则更新失败,当前线程可选择重试或放弃操作。这种机制通过硬件级别的原子指令实现,避免了传统锁机制带来的线程阻塞和上下文切换开销,显著提升并发性能。

二、CAS在Java 中的实现:通过Unsafe 类与底层硬件协同实现

在 Java 中,CAS 操作的实现依赖于sun.misc.Unsafe类。该类提供了compareAndSwapObject、compareAndSwapInt、compareAndSwapLong等方法,用于对不同类型的共享变量执行 CAS 操作。值得注意的是,Unsafe类中的 CAS 方法均为native方法,其底层实现依赖于操作系统和 CPU 架构,通过 JNI(Java Native Interface)调用 C/C++ 代码实现。

以AtomicInteger为例,其核心源码展示了 CAS 的具体应用:

public class AtomicInteger extends Number implements java.io.Serializable {private static final long serialVersionUID = 6214790243416807050L;// 获取Unsafe类实例private static final Unsafe unsafe = Unsafe.getUnsafe();// 存储value字段的内存偏移量private static final long valueOffset;static {try {// 初始化时获取value字段的内存偏移量valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));} catch (Exception ex) {throw new Error(ex);}}// 使用volatile保证可见性private volatile int value;// 通过CAS机制更新valuepublic final boolean compareAndSet(int expect, int update) {return unsafe.compareAndSwapInt(this, valueOffset, expect, update);}
}

上述代码中,unsafe.compareAndSwapInt方法通过内存偏移量valueOffset直接定位变量在内存中的地址,并执行原子更新操作。这一过程依赖于底层 CPU 提供的原子指令,如 x86 架构下的CMPXCHG指令,确保同一时刻只有一个线程能够修改目标内存地址。

注:

  • 偏移量:在 CAS(Compare and Swap)操作中,偏移量指的是对象属性在内存中的相对位置。Java 通过 Unsafe 类提供的objectFieldOffset方法获取目标属性相对于对象起始地址的偏移量,这个偏移量是实现原子更新的关键。例如,在AtomicInteger中对value字段进行 CAS 更新时,首先需要获取value字段的偏移量,后续的比较和替换操作都是基于这个偏移量直接在内存层面进行,绕过了传统锁机制带来的性能损耗,从而实现高效的并发数据修改 。

三、CAS 在 Java 中的应用:通过atomic类调用Unsafe类实现原子操作

3.1 原子类的底层实现原理

在java.util.concurrent.atomic包中,AtomicInteger、AtomicLong、AtomicReference等原子类均以 CAS 机制作为核心实现,源码调用了Unsafe类码。这些原子类采用无锁自旋重试策略,通过循环尝试更新共享变量,直到操作成功。这种设计巧妙地避免了传统锁机制导致的线程阻塞问题。在高并发场景下,原子类通过减少锁竞争带来的开销,显著提升了数据更新的效率。

AtomicInteger 核心源码部分

// 获取Unsafe实例
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;static {try {// 获取“value”字段在AtomicInteger类中的内存偏移量valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));} catch (Exception ex) {throw new Error(ex);}
}// 确保“value”字段的可见性
private volatile int value;/*** 如果当前值等于预期值,则原子地将值设置为newValue* 使用Unsafe#compareAndSwapInt方法进行CAS操作*/
public final boolean compareAndSet(int expect, int update) {return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}/*** 原子地将当前值加delta并返回旧值*/
public final int getAndAdd(int delta) {return unsafe.getAndAddInt(this, valueOffset, delta);
}/*** 原子地将当前值加1并返回加之前的值(旧值)* 使用Unsafe#getAndAddInt方法进行CAS操作*/
public final int getAndIncrement() {return unsafe.getAndAddInt(this, valueOffset, 1);
}/*** 原子地将当前值减1并返回减之前的值(旧值)*/
public final int getAndDecrement() {return unsafe.getAndAddInt(this, valueOffset, -1);
}

Unsafe#getAndAddInt 源码部分

/*** 原子地获取并递增指定对象的整数值* @param o 目标对象* @offset 内存偏移量* @delta 增量值* @return 修改前的旧值*/
public final int getAndAddInt(Object o, long offset, int delta) {int v;do {// 以 volatile 语义获取当前值v = getIntVolatile(o, offset);} while (!compareAndSwapInt(o, offset, v, v + delta)); // CAS 更新直到成功return v;
}

请注意,上述代码中直接使用Unsafe.getUnsafe()在正常 Java 应用中可能会抛出安全异常,因为Unsafe类主要用于底层、高风险的操作,一般在 Java 核心库内部使用 。

3.2 非阻塞数据结构的实现

CAS 机制是构建非阻塞数据结构的基石,典型代表包括ConcurrentLinkedQueue和ConcurrentSkipListMap。在这些数据结构中,节点的插入、删除与修改操作均依赖 CAS 指令完成。通过直接比较并更新内存中的节点引用,在无需加锁的前提下,确保数据操作的原子性与线程安全性,实现高效的并发访问控制。

四、CAS 的问题与解决方案

4.1 ABA 问题:数据一致性的潜在威胁

问题描述:当线程 A 读取到变量值为 A,在准备更新为 B 之前,其他线程将变量值从 A 修改为 B 再改回 A。此时线程 A 执行 CAS 操作时,由于当前值与预期值均为 A,操作成功,但实际数据已被修改,可能导致业务逻辑错误。

解决方案

  1. 版本号机制:为变量添加版本号,每次修改时版本号递增。CAS 操作时同时比较版本号和变量值,确保数据的一致性。
  1. 时间戳机制:使用时间戳记录变量的修改时间,类似版本号机制,通过对比时间戳判断数据是否被篡改。

4.2 自旋开销:CPU 资源的过度消耗

问题描述:CAS 操作通常采用自旋重试策略,若长时间更新失败,将导致 CPU 资源被无效占用,影响系统整体性能。

解决方案

  1. 引入pause指令:现代 CPU 支持pause指令,在自旋失败时让 CPU 短暂休眠,降低内存访问频率,减少流水线重排带来的开销。
  1. 自适应自旋:根据历史操作成功率动态调整自旋次数,避免无限循环。

4.3 单变量限制:复杂场景的局限性

问题描述:CAS 仅支持对单个共享变量的原子操作,无法直接处理多个变量的组合更新场景。

解决方案

  1. 封装对象:使用AtomicReference将多个变量封装为一个对象,通过操作对象实现原子更新。
  1. 分段锁机制:将数据划分为多个段,每个段独立使用 CAS 操作,减少锁竞争范围。

五、博主总结

  • CAS(属于锁机制里面的内容)
    • CAS基本(是什么?机制是什么?)
      • CAS 的全称是 Compare And Swap(比较与交换),核心是实现原子性的更新。
      • CAS 的思想很简单,即只有当内存中的值等于预期值时,才更新为新值,否则不更新。整个过程是原子的,不会被其他线程打断。
      • CAS 涉及到三个操作数:
        • V:要更新的变量值(Var)
        • E:预期值(Expected)
        • N:拟写入的新值(New)
      • 当且仅当 V 的值等于 E 时,CAS 通过原子方式用新值 N 来更新 V 的值。如果不等,说明已经有其它线程更新了 V,则当前线程放弃更新。
      • 注意:当多个线程同时使用 CAS 操作一个变量时,只有一个会成功更新,其余均会失败,但失败的线程并不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。
    • java中CAS是如何实现的?(Unsafe类)
      • sun.misc包下的Unsafe类,这个类是一个提供低级别、不安全操作的类。由于其强大的功能和潜在的危险性,它通常用于 JVM 内部或一些需要极高性能和底层访问的库中,而不推荐普通开发者在应用程序中使用。Unsafe类提供了compareAndSwapObject、compareAndSwapInt、compareAndSwapLong方法来实现的对Object、int、long类型的 CAS 操作。

      • Unsafe类中的 CAS 方法是native方法。native关键字表明这些方法是用本地代码(通常是 C 或 C++)实现的,而不是用 Java 实现的。这些方法直接调用底层的硬件指令来实现原子操作。也就是说,Java 语言并没有直接用 Java 实现 CAS。
      • 更准确点来说,Java 中 CAS 是 C++ 内联汇编的形式实现的,通过 JNI(Java Native Interface) 调用。因此,CAS 的具体实现与操作系统以及 CPU 密切相关。
      • 底层实现。

    • Java 如何使用Unsafe类的方法来实现原子操作?(通过Atomic类调用)
      • Atomic类依赖于 CAS乐观锁来保证其方法的原子性,而不需要使用传统的锁机制(如 synchronized 块或 ReentrantLock)。
      • AtomicInteger是 Java 的原子类之一,主要用于对 int 类型的变量进行原子操作,它利用Unsafe类提供的低级别原子操作方法实现无锁的线程安全性。
      • 附上核心源码分析助于理解

    • CAS算法存在哪些问题?
      • ABA问题
        • 是什么?ABA问题是在使用CAS时可能出现的一种并发问题。在多线程环境下,如果一个变量的值先被线程A修改为B,然后又被线程B修改回A,那么在使用CAS进行比较和交换操作时,尽管变量的当前值与预期值相同(都是A),但实际上这个变量的值已经被修改过,这就是ABA问题。
        • 怎么解决?在变量前面追加上版本号或者时间戳。
      • 循环时间长开销大
        • 是什么?CAS 经常会用到自旋操作来进行重试,也就是不成功就一直循环执行直到成功。如果长时间不成功,会给 CPU 带来非常大的执行开销。
        • 怎么解决?解决思路是让 JVM 支持处理器提供的pause 指令。pause 指令能让自旋失败时 cpu 睡眠一小段时间再继续自旋,从而使得读操作的频率降低很多,为解决内存顺序冲突而导致的 CPU 流水线重排的代价也会小很多。
      • 只能保证一个共享变量的原子操作
        • 是什么?CAS 操作仅能对单个共享变量有效。当需要操作多个共享变量时,CAS 就显得无能为力。
        • 怎么解决?Java 提供了AtomicReference类,通过将多个变量封装在一个对象中,我们可以使用AtomicReference(原子性)来执行 CAS 操作。还可以利用加锁来保证但是性能差。
    • 有的同学会问为什么在解决ABA问题时,CAS都加上版本号或时间戳了,那为什么不直接只使用一个版本号法去实现线程安全呢?
      • 原因在于 CAS 的核心目标不仅仅是线程安全,而是 原子性更新:机制是先进行一致性校验,后进行通过java的Atomic类调用Unsafe类进行原子性更新操作。比较并交换,是原子性更新的基础。
      • 什么是原子性更新?
        • 不可分割性:操作要么全部完成,要么全部不完成。
        • 中间状态不可见:其他线程无法看到操作的中间状态。
        • 线程安全:无需额外同步机制即可保证正确性。
      • 那他是怎么保证原子性更新的呢?
        • 自旋重试​:如果CAS失败(值被其他线程修改),则重试,直到成功。
        • Unsafe 类通过 JNI (Java Native Interface,Java 本地接口)调用本地方法,直接操作内存。底层调用(如x86的CMPXCHG),CPU会加锁确保同一时间只有一个线程能修改该内存地址。
        • volatile保证可见性​:确保多线程能立即看到最新值。
    • Unsafe类和Atomic类详解
      • Java 魔法类 Unsafe 详解(关于实现CAS)
      • Atomic 原子类总结(关于乐观锁针对的是单个共享变量、以及实现CAS)
    • 偏移量offset,使用偏移量直接操作内存,实现原子性更新。
      • CAS 会先读取字段的实际值(通过偏移量 valueOffset 定位内存地址)。

相关文章:

  • 一、内存调优
  • 低功耗:XILINX FPGA如何优化功耗?
  • 人工智能(AI)与BIM:建筑业创新实践的深度融合
  • 供应商管理有哪些风险点?
  • C++11特性
  • HTML向四周扩散背景
  • DriveGenVLM:基于视觉-语言模型的自动驾驶真实世界视频生成
  • C#中的ThreadStart委托
  • 代理IP高可用性与稳定性方案:负载均衡、节点健康监测与智能切换策略
  • LLaMA-Factory:了解webUI参数
  • 【hive】hive内存dump导出hprof文件
  • 虚幻引擎5-Unreal Engine笔记之什么时候新建GameMode,什么时候新建关卡?
  • solidity智能合约-知识点
  • 开源音视频转文字工具:基于 Vosk 和 Whisper 的多语言语音识别项目
  • B/S架构和C/S架构的介绍与分析
  • 如何在LVGL之外的线程更新UI内容
  • 从纸质契约到智能契约:AI如何改写信任规则与商业效率?​——从智能合约到监管科技,一场颠覆传统商业逻辑的技术革命
  • Unreal 从入门到精通之SceneCaptureComponent2D实现UI层3D物体360°预览
  • 学习VS2022离线安装包的下载方法
  • STC-ISP烧录过程中一直显示“正在检测单片机”的解决办法
  • 欧洲加大力度招募美国科研人员
  • 中国首次当选联合国教科文组织1970年《公约》缔约国大会主席国
  • 谷神星一号海射型遥五运载火箭发射成功
  • 倒计时1天:走进“中国荔乡”茂名,探寻农交文旅商融合发展新模式
  • 央媒:设施老化、应急预案套模板,养老机构消防隐患亟待排查
  • 第十届青春文学奖揭晓,梁晓声获特别奖