在Java中CAS机制是如何工作的_Java无锁并发底层原理解析

发布时间 - 2026-01-27 00:00:00    点击率:
CAS通过CPU硬件指令(如x86的CMPXCHG)在一个不可中断周期内原子执行“读值→比较→写值”,依赖volatile保证可见性,无需锁和上下文切换。

CAS 是怎么做到“比较+交换”一步到位的

CAS 不是 Java 语言层面的语法或关键字,而是由 CPU 硬件指令(如 x86 上的 CMPXCHG)直接支持的原子操作。JVM 通过 Unsafe 类暴露了 compareAndSwapIntcompareAndSwapObject 等 native 方法,这些方法最终调用操作系统底层的汇编指令,在一个不可中断的 CPU 周期内完成「读内存值 → 比较 → 写新值」三步——中间不会被线程调度打断。

关键点在于:它依赖 volatile 变量语义保证可见性(每次读都是从主内存取),再靠硬件指令保证原子性。没有锁、没有阻塞、也没有上下文切换开销。

  • 不是 Java 字节码实现的,无法在纯 Java 层模拟;必须走 Unsafe + JNI + 底层汇编链路
  • 所有 AtomicIntegerAtomicReference 等原子类的 incrementAndGet()compareAndSet() 都基于这套机制
  • 如果你看到 getAndAddInt 方法里那个经典的 do-while 循环,那正是 CAS 失败后自旋重试的体现

为什么 AtomicInteger.addAndGet() 不会丢数据

因为它的底层逻辑本质上是「乐观重试」:先读当前值 oldValue,算出 newValue = oldValue + delta,再用 CAS 尝试把内存位置的值从 oldValue 改成 newValue。只要期间有别的线程抢先改了这个值,CAS 就失败,循环重新读、再算、再试。

public final int addAndGet(int delta) {
    int var1;
    do {
        var1 = this.getIntVolatile(valueOffset);
    } while(!this.compareAndSwapInt(valueOffset, var1, var1 + delta));
    return var1 + delta;
}
  • 如果两个线程同时执行 addAndGet(1),初始值为 0,一个成功把 0→1,另一个读到的是 1 而非 0,CAS 失败后继续重试,最终结果仍是 2
  • 这不是“避免竞争”,而是“接受竞争并靠重试解决”——所以高并发下可能自旋很久,吃 CPU
  • volatile 保证每次 getIntVolatile() 都能拿到最新值;否则可能一直读到过期副本,导致无限重试

ABA 问题不是理论陷阱,是真实会崩业务的 Bug

假设一个线程读到值 A,准备 CAS 成 B;但此时另一个线程把 A→B→A 改了两轮。原线程的 CAS 仍会成功,因为它只比数值,不比“有没有被动过”。这在引用类型中尤其危险——比如栈顶节点被弹出又压入同一个对象,表面值没变,实际已不是同一个逻辑状态。

  • 典型场景:AtomicReference 管理对象引用时,若

    对象被复用(如对象池),极易触发 ABA
  • Java 提供了 AtomicStampedReferenceAtomicMarkableReference 来带版本号/标记位做双重校验
  • 别指望靠加日志或 sleep 规避——ABA 是竞态本质,只能靠带版本的 CAS 或换用锁

别在循环体外手动写 CAS 自旋,除非你真懂代价

很多人看到 CAS 原理后,会手写类似 while (!ref.compareAndSet(old, new)) { old = ref.get(); } 的代码。这看似简洁,但容易忽略几个硬伤:

  • 没有退避策略:CPU 空转自旋在高争用下会打满核心,而 AtomicInteger 等类内部其实做了轻量级 pause 优化(x86 上是 PAUSE 指令)
  • 没考虑线程优先级反转风险:低优先级线程长时间占着 CPU 自旋,高优先级线程反而卡住
  • 多数业务场景根本不需要裸 CAS——ConcurrentHashMapLongAdderStampedLock 已经封装了更稳的模式

真正该自己上 CAS 的情况极少:比如写高性能 RingBuffer、无锁队列,或定制化状态机。其余时候,优先用标准库提供的原子类或并发容器。


# java  # 操作系统  # 字节  #   # 无锁  # 标准库  # 为什么  # 有锁  # jvm  # while  # 封装  # 子类  # volatile  # 循环  # 引用类型  # 线程  # 并发  # 对象  # bug  # 重试  # 读到  # 改了  # 的是  # 几个  # 如果你  # 见性  # 不需要  # 都能 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: 百度输入法全感官ai怎么关 百度输入法全感官皮肤关闭  Laravel如何创建自定义中间件?(Middleware代码示例)  Laravel如何为API生成Swagger或OpenAPI文档  简历没回改:利用AI润色让你的文字更专业  软银砸40亿美元收购DigitalBridge 强化AI资料中心布局  Laravel Artisan命令怎么自定义_创建自己的Laravel命令行工具完全指南  Laravel怎么实现前端Toast弹窗提示_Laravel Session闪存数据Flash传递给前端【方法】  Laravel如何构建RESTful API_Laravel标准化API接口开发指南  Laravel的.env文件有什么用_Laravel环境变量配置与管理详解  免费的流程图制作网站有哪些,2025年教师初级职称申报网上流程?  深入理解Android中的xmlns:tools属性  深圳网站制作的公司有哪些,dido官方网站?  如何用好域名打造高点击率的自主建站?  免费网站制作appp,免费制作app哪个平台好?  三星网站视频制作教程下载,三星w23网页如何全屏?  在centOS 7安装mysql 5.7的详细教程  html5源代码发行怎么设置权限_访问权限控制方法与实践【指南】  JavaScript如何实现错误处理_try...catch如何捕获异常?  胶州企业网站制作公司,青岛石头网络科技有限公司怎么样?  Laravel的HTTP客户端怎么用_Laravel HTTP Client发起API请求教程  如何在阿里云购买域名并搭建网站?  Windows10电脑怎么查看硬盘通电时间_Win10使用工具检测磁盘健康  Laravel中Service Container是做什么的_Laravel服务容器与依赖注入核心概念解析  如何在IIS中新建站点并配置端口与IP地址?  Laravel如何实现URL美化Slug功能_Laravel使用eloquent-sluggable生成别名【方法】  如何在万网ECS上快速搭建专属网站?  如何在 React 中条件性地遍历数组并渲染元素  简单实现Android文件上传  如何用5美元大硬盘VPS安全高效搭建个人网站?  Laravel怎么实现验证码功能_Laravel集成验证码库防止机器人注册  Laravel Eloquent:优雅地将关联模型字段扁平化到主模型中  浅析上传头像示例及其注意事项  香港服务器建站指南:外贸独立站搭建与跨境电商配置流程  网站制作大概多少钱一个,做一个平台网站大概多少钱?  详解MySQL数据库的安装与密码配置  如何用AWS免费套餐快速搭建高效网站?  如何快速搭建虚拟主机网站?新手必看指南  Laravel策略(Policy)如何控制权限_Laravel Gates与Policies实现用户授权  极客网站有哪些,DoNews、36氪、爱范儿、虎嗅、雷锋网、极客公园这些互联网媒体网站有什么差异?  太平洋网站制作公司,网络用语太平洋是什么意思?  如何用景安虚拟主机手机版绑定域名建站?  如何快速查询网站的真实建站时间?  Laravel Livewire是什么_使用Laravel Livewire构建动态前端界面  如何在宝塔面板中创建新站点?  悟空识字怎么关闭自动续费_悟空识字取消会员自动扣费步骤  Laravel怎么做数据加密_Laravel内置Crypt门面的加密与解密功能  用yum安装MySQLdb模块的步骤方法  简单实现jsp分页  如何在橙子建站中快速调整背景颜色?  如何在浏览器中启用Flash_2025年继续使用Flash Player的方法【过时】