Java中的CAS机制详解

Java中的CAS机制详解

Java CAS(Compare And Swap)机制详解

一、概述

CAS(Compare And Swap,比较并交换) 是一种无锁(Lock-Free)的多线程同步机制,它基于硬件提供的原子性操作来实现线程安全。CAS 是现代并发编程中的核心技术,广泛应用于 Java 并发包中。

二、核心原理

1. 操作语义

CAS 操作包含三个参数:

  • V:要更新的内存地址(变量)
  • A:旧的预期值(Expected Value)
  • B:要设置的新值(New Value)

2. 执行流程

booleanCAS(V,A,B){if(V==A){// 比较:当前值是否等于预期值V=B;// 交换:设置新值returntrue;// 成功}else{returnfalse;// 失败}}

3. 硬件支持

CAS 操作在硬件层面是原子的,主要通过以下方式实现:

  • x86/64 架构CMPXCHG(Compare and Exchange)指令
  • ARM 架构LDREX/STREX(Load-Exclusive/Store-Exclusive)指令
  • MIPS 架构LL/SC(Load-Linked/Store-Conditional)指令

三、Java 中的 CAS 实现

1. sun.misc.Unsafe 类

Java 通过 Unsafe 类提供底层 CAS 操作:

importsun.misc.Unsafe;importjava.lang.reflect.Field;publicclassCASExample{privatestaticUnsafe unsafe;privatevolatileint value;privatestaticlong valueOffset;static{try{Field field =Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); unsafe =(Unsafe) field.get(null); valueOffset = unsafe.objectFieldOffset(CASExample.class.getDeclaredField("value"));}catch(Exception e){thrownewError(e);}}// 自定义 CAS 操作publicbooleancompareAndSet(int expectedValue,int newValue){return unsafe.compareAndSwapInt(this, valueOffset, expectedValue, newValue);}}

2. java.util.concurrent.atomic 包

Java 提供了丰富的原子类,内部均基于 CAS 实现:

类名描述常用方法
AtomicInteger原子整型get(), set(), compareAndSet(), incrementAndGet()
AtomicLong原子长整型get(), addAndGet(), compareAndSet()
AtomicBoolean原子布尔型get(), compareAndSet()
AtomicReference<V>原子引用get(), set(), compareAndSet()
AtomicIntegerArray原子整型数组getAndSet(), compareAndSet()
AtomicStampedReference<V>带版本号的原子引用解决 ABA 问题

四、完整使用示例

示例 1:基础 CAS 操作

importjava.util.concurrent.atomic.AtomicInteger;publicclassBasicCASExample{privateAtomicInteger counter =newAtomicInteger(0);publicvoidincrement(){int oldValue, newValue;do{ oldValue = counter.get();// 读取当前值 newValue = oldValue +1;// 计算新值}while(!counter.compareAndSet(oldValue, newValue));// CAS循环直到成功}publicintgetValue(){return counter.get();}publicstaticvoidmain(String[] args)throwsInterruptedException{BasicCASExample example =newBasicCASExample();// 创建10个线程并发递增Thread[] threads =newThread[10];for(int i =0; i <10; i++){ threads[i]=newThread(()->{for(int j =0; j <1000; j++){ example.increment();}}); threads[i].start();}for(Thread t : threads){ t.join();}System.out.println("最终结果: "+ example.getValue());// 输出: 10000}}

示例 2:实现线程安全的栈

importjava.util.concurrent.atomic.AtomicReference;publicclassConcurrentStack<T>{privatestaticclassNode<T>{finalT value;Node<T> next;Node(T value){this.value = value;}}privateAtomicReference<Node<T>> top =newAtomicReference<>();// 无锁push操作publicvoidpush(T value){Node<T> newNode =newNode<>(value);Node<T> oldHead;do{ oldHead = top.get();// 读取当前栈顶 newNode.next = oldHead;// 新节点指向旧栈顶}while(!top.compareAndSet(oldHead, newNode));// CAS更新栈顶}// 无锁pop操作publicTpop(){Node<T> oldHead, newHead;do{ oldHead = top.get();// 读取当前栈顶if(oldHead ==null){returnnull;// 栈为空} newHead = oldHead.next;// 新栈顶为下一个节点}while(!top.compareAndSet(oldHead, newHead));// CAS更新栈顶return oldHead.value;}publicbooleanisEmpty(){return top.get()==null;}}

示例 3:实现自旋锁

importjava.util.concurrent.atomic.AtomicBoolean;publicclassSpinLock{privateAtomicBoolean locked =newAtomicBoolean(false);publicvoidlock(){// 自旋等待直到获取锁while(!locked.compareAndSet(false,true)){// 可添加Thread.yield()或等待策略减少CPU占用Thread.yield();}}publicvoidunlock(){ locked.set(false);}publicbooleantryLock(){return locked.compareAndSet(false,true);}}

示例 4:带版本号的 CAS(解决 ABA 问题)

什么是ABA问题见我的另一篇文章:ABA问题详解(Java)

importjava.util.concurrent.atomic.AtomicStampedReference;publicclassABASolutionExample{publicstaticvoidmain(String[] args){// 创建初始值,使用静态变量避免自动装箱创建新对象finalInteger value100 =100;finalInteger value200 =200;finalInteger value300 =300;// 初始值100,版本号0AtomicStampedReference<Integer> ref =newAtomicStampedReference<>(value100,0);int[] stampHolder =newint[1];int oldValue = ref.get(stampHolder);int oldStamp = stampHolder[0];System.out.println("初始: 值="+ oldValue +", 版本="+ oldStamp);// 模拟ABA// ====================================================================// 注意:不能写成这样,否则Integer对象会被缓存,导致版本号不变// 步骤1:100 → 200 ref.compareAndSet(100, 200, 0, 1); 这里的100和200都是自动装箱的Integer对象// 步骤2:200 → 100 ref.compareAndSet(200, 100, 1, 2); 这里的200是新创建的Integer对象,与步骤1中的200不是同一个对象// ====================================================================// A -> Bint[] stampHolder1 =newint[1];Integer currentValue1 = ref.get(stampHolder1); ref.compareAndSet(currentValue1, value200, stampHolder1[0], stampHolder1[0]+1);// B -> Aint[] stampHolder2 =newint[1];Integer currentValue2 = ref.get(stampHolder2); ref.compareAndSet(currentValue2, value100, stampHolder2[0], stampHolder2[0]+1);// 尝试CAS(会失败,因为版本变了)boolean success = ref.compareAndSet(oldValue, value300, oldStamp, oldStamp +1);System.out.println("结果: "+(success ?"成功":"失败"));System.out.println("原因: 版本从"+ oldStamp +"变为"+ ref.getStamp());}}

五、CAS 的优缺点

优点

  1. 高性能:无锁操作,避免线程上下文切换
  2. 非阻塞:线程不会挂起,减少死锁风险
  3. 可扩展性好:在高并发场景下性能优势明显

缺点

  1. ABA 问题:值从 A 改为 B 再改回 A,CAS 无法察觉
  2. 自旋开销:竞争激烈时 CPU 空转浪费资源
  3. 只能保证一个变量的原子性:无法保证多个变量的复合操作
  4. 实现复杂:正确实现无锁算法难度较大

六、CAS 与锁的性能对比

importjava.util.concurrent.CountDownLatch;importjava.util.concurrent.atomic.AtomicInteger;importjava.util.concurrent.locks.ReentrantLock;publicclassCASvsLockBenchmark{privatestaticfinalint THREAD_COUNT =10;privatestaticfinalint ITERATIONS =1000000;// 测试方法privatestaticlongbenchmark(Runnable task)throwsInterruptedException{CountDownLatch latch =newCountDownLatch(THREAD_COUNT);Thread[] threads =newThread[THREAD_COUNT];long start =System.currentTimeMillis();for(int i =0; i < THREAD_COUNT; i++){ threads[i]=newThread(()->{ task.run(); latch.countDown();}); threads[i].start();} latch.await();long end =System.currentTimeMillis();return end - start;}publicstaticvoidmain(String[] args)throwsInterruptedException{// 1. 测试 synchronizedObject lock =newObject();int[] syncCounter ={0};long syncTime =benchmark(()->{for(int j =0; j < ITERATIONS; j++){synchronized(lock){ syncCounter[0]++;}}});// 2. 测试 ReentrantLockReentrantLock reentrantLock =newReentrantLock();int[] lockCounter ={0};long lockTime =benchmark(()->{for(int j =0; j < ITERATIONS; j++){ reentrantLock.lock();try{ lockCounter[0]++;}finally{ reentrantLock.unlock();}}});// 3. 测试 CAS (AtomicInteger)AtomicInteger atomicCounter =newAtomicInteger(0);long atomicTime =benchmark(()->{for(int j =0; j < ITERATIONS; j++){ atomicCounter.incrementAndGet();}});System.out.println("测试结果(越低越好):");System.out.println("synchronized: "+ syncTime +"ms");System.out.println("ReentrantLock: "+ lockTime +"ms");System.out.println("CAS (AtomicInteger): "+ atomicTime +"ms");// 验证结果System.out.println("\n最终计数验证:");System.out.println("synchronized: "+ syncCounter[0]);System.out.println("ReentrantLock: "+ lockCounter[0]);System.out.println("CAS: "+ atomicCounter.get());}}

典型测试结果(10个线程,每个递增100万次):

  • synchronized: ~1200ms
  • ReentrantLock: ~800ms
  • CAS (AtomicInteger): ~400ms

七、CAS 的适用场景

适合使用 CAS 的场景

  1. 计数器AtomicIntegerAtomicLong
  2. 状态标志AtomicBoolean
  3. 无锁数据结构:栈、队列、集合
  4. 资源引用管理:对象池、连接池
  5. 版本控制:乐观锁实现

不适合使用 CAS 的场景

  1. 复杂业务逻辑:需要多个变量原子更新
  2. 竞争激烈场景:大量线程频繁 CAS 失败
  3. 需要阻塞等待:线程需要等待某些条件

八、最佳实践

1. 使用现有原子类

// ✅ 优先使用 JDK 提供的原子类AtomicInteger counter =newAtomicInteger(0);AtomicReference<User> userRef =newAtomicReference<>();

2. 避免过度自旋

// ❌ 纯自旋,CPU 消耗大while(!atomicRef.compareAndSet(oldValue, newValue)){// 空循环}// ✅ 添加退避策略int retries =0;while(!atomicRef.compareAndSet(oldValue, newValue)){if(retries++>100){Thread.yield();}// 或者使用指数退避}

3. 解决 ABA 问题

// 使用带版本号的原子引用AtomicStampedReference<Integer> ref =newAtomicStampedReference<>(0,0);// 或者使用 AtomicMarkableReferenceAtomicMarkableReference<Integer> markedRef =newAtomicMarkableReference<>(0,false);

4. 组合操作使用循环 CAS

publicvoidaddIfLessThan(int max){int current;int newValue;do{ current = atomicInt.get();if(current >= max){return;// 条件不满足} newValue = current +1;}while(!atomicInt.compareAndSet(current, newValue));}

九、总结

CAS 是现代并发编程的基石,它通过硬件支持的原子操作实现了高效的无锁同步。在实际开发中:

  1. 优先选择:使用 java.util.concurrent.atomic 包提供的原子类
  2. 注意限制:理解 CAS 的局限,特别是 ABA 问题和自旋开销
  3. 性能考量:在低到中度竞争场景使用 CAS,高度竞争时考虑其他方案
  4. 正确使用:复杂操作使用循环 CAS 模式,必要时添加退避策略

通过合理使用 CAS,可以在保持线程安全的同时获得比传统锁机制更好的性能,特别是在多核处理器和高并发场景下。

Read more

【C++】智能指针

【C++】智能指针

前言         上文我们学到了C++11的异常,了解到了C++与C语言处理错误的区别,异常的特点在于抛出与接收。【C++11】异常-ZEEKLOG博客         本文我们来学习C++中的下一个功能:智能指针 1.智能指针的使用场景         在上文我们知道了抛异常的知识,抛异常的“抛”这个动作一般来说是当程序出现了错误,抛出错误信息为了让我们解决。这个原本是解决错误的动作,在某些时候却称为了“铸就”错误的是罪魁祸首。         比如:我们知道执行throw,这意味着在这个局部域中throw后面的语句将不再执行,跳过一段又一段程序直到找到匹配的catch时,才会从catch这个语句进行向下执行。那么一个局部域中如果在抛出异常时申请了空间,明明可以正常销毁的,但是却因为抛异常跳过了销毁空间的语句。这就导致一个及其严重的事故:内存泄漏!         在此之前,为了防止出现内存泄漏。我们通常是将抛出的异常再次捕获,执行销毁语句后,将异常重新抛出。但是这种方法并不太好用,所以为了更好的解决这个问题:智能指针诞生了。 2.RAII和智能指针的设计

By Ne0inhk
C++的IO流和C++的类型转换----《Hello C++ Wrold!》(29)--(C/C++)

C++的IO流和C++的类型转换----《Hello C++ Wrold!》(29)--(C/C++)

文章目录 * 前言 * C++的类型转换 * 四种命名的强制类型转换操作符 * static_cast * reinterpret_cast * const_cast * dynamic_cast * RTTI(这个了解一下就行了) * C++的IO流 * C++文件的IO流 * stringstream 前言 在 C++ 编程体系中,类型转换与 IO 流是支撑程序数据处理与交互的两大核心环节。类型转换关乎数据在不同类型间的安全传递与运算适配,而 IO 流则负责程序与外部设备(如键盘、屏幕、文件)之间的数据输入与输出,二者共同构成了 C++ 程序实现功能、交互信息的基础框架。 C 语言中的类型转换方式虽简洁,却存在可视性差、难以追踪的问题,容易在复杂程序中引发潜在的逻辑错误。为解决这一痛点,C++ 引入了四种命名明确的强制类型转换操作符 ——static_cast、reinterpret_

By Ne0inhk

C++ 有哪些性能分析工具?

2026年 C++ 性能分析(Profiling)工具全景(基于当前社区共识与生产实践) C++ 性能分析工具主要分为几大类:采样型(Sampling)、插桩型(Instrumentation)、内存专用、硬件级深度分析、火焰图/可视化等。 下面按实用性、流行度、使用场景排序,列出目前最常用、最推荐的工具(2025-2026年真实开发者反馈): 排名工具名称类型平台支持开源/免费核心优势(2026现状)主要缺点推荐场景学习曲线1perf (Linux kernel)采样 + 硬件事件Linux开源免费轻量、无侵入、硬件PMU支持极好、火焰图生态完善无GUI(需配合Hotspot/FlameGraph)Linux服务器/嵌入式/高性能后台首选★★☆2Intel VTune Profiler采样+插桩+硬件深度Windows/Linux(macOS查看器)免费(oneAPI)硬件级分析最强(cache miss、

By Ne0inhk
Redis核心通用命令深度解析:结合C++ redis-plus-plus 实战指南

Redis核心通用命令深度解析:结合C++ redis-plus-plus 实战指南

前言:为何选择 Redis 与 C++? 在当今这个数据驱动的时代,高性能的数据存储与访问是构建现代化应用的基石。Redis,作为一个开源的、基于内存的键值对存储数据库,以其无与伦比的读写速度、丰富的数据结构、以及灵活的应用场景(缓存、消息队列、会话存储、排行榜等),成为了后端开发者的瑞士军刀。 与此同时,C++ 作为一门追求极致性能的编程语言,长期以来在游戏开发、金融交易、高性能计算等领域占据着主导地位。当 C++ 的高性能与 Redis 的高速度相结合时,我们便能够构建出响应迅捷、吞吐量巨大的应用程序。 然而,要将二者优雅地结合起来,我们需要一个强大的“桥梁”——Redis 客户端库。redis-plus-plus 就是这样一个专为现代 C++ (C++11 及以上) 设计的优秀库。它不仅封装了 Redis 的原生协议,提供了类型安全、易于使用的 API,

By Ne0inhk