文章目录
- 1 前言:从简单的计数器问题说起
- 2 什么是CAS?一个拍卖会的比喻
- 3 CAS的底层原理:从Java代码到CPU指令
- 3.1 Java层面的CAS实现
- 3.2 深入硬件:CPU指令层面的实现
- 4 CAS在Java中的应用场景
- 4.1 原子类:AtomicInteger等
- 4.2 自旋锁实现
- 4.3 限流器中的CAS应用
- 5 CAS的三大问题及解决方案
- 5.1 ABA问题
- 5.2 循环时间长导致CPU开销大
- 5.3 只能保证一个变量的原子操作
- 6 CAS与同步锁的性能对比
- 7 实战经验与最佳实践
- 7.1 选择合适的并发控制
- 7.2 避免常见陷阱
- 8 结语:无锁编程的未来
- 参考资料
大家好,我是你们的技术老友科威舟,今天跟大家聊聊Java并发编程中的CAS原理。
1 前言:从简单的计数器问题说起
想象一下,你和几位朋友一起记录网站访问量,每人面前都有一个计数器。每次有人访问网站,你们需要同时增加计数器的值。如果使用传统的i++方式,很可能出现两人同时读取同一个值,然后分别加1后写入,导致实际计数少于应有值的情况。这就是典型的并发计数问题。
在Java中,我们可能会尝试用synchronized关键字解决这个问题,但它像一把重量级锁,会让其他线程阻塞等待,性能开销较大。那么有没有更高效的方法呢?这就是我们今天要介绍的CAS(Compare And Swap)技术,它堪称Java并发包的无名英雄。
2 什么是CAS?一个拍卖会的比喻
CAS的全称是Compare And Swap(比较并交换),它是一种无锁算法,用于在多线程环境下实现变量的原子性更新。
想象一场拍卖会:拍卖师宣布当前最高价是100元(内存中的当前值)。你举牌报价150元(新值),但在此之前,需要确认是否有人已经出价超过100元。如果中间有人出价120元,你的150元报价就需要基于120元重新计算。CAS操作就像这个过程:查看当前值是否与预期值相同,如果相同才更新为新值。
CAS操作涉及三个基本操作数:
- V:要更新的变量内存地址
- A:旧的预期值(你认为当前应该的值)
- B:要设置的新值
CAS的伪代码逻辑如下:
booleancompareAndSwap(intexpectedValue,intnewValue){if(memoryValue==expectedValue){memoryValue=newValue;returntrue;}returnfalse;}3 CAS的底层原理:从Java代码到CPU指令
3.1 Java层面的CAS实现
在Java中,CAS操作主要通过sun.misc.Unsafe类提供的方法实现(JDK9+推荐使用VarHandle)。原子类AtomicInteger的incrementAndGet()方法正是基于CAS实现的:
publicfinalintincrementAndGet(){intprev,next;do{prev=get();// 获取当前值next=prev+1;// 计算新值}while(!compareAndSet(prev,next));// CAS更新returnnext;}3.2 深入硬件:CPU指令层面的实现
Java代码中的CAS操作最终会转换为底层CPU指令。在x86架构中,对应的指令是cmpxchg(compare and exchange),但仅仅这条指令还不足以保证原子性,需要加上lock前缀来锁定总线或使用缓存锁定机制。
完整的调用链是这样的:
// Java层:Unsafe类unsafe.compareAndSwapInt(obj,offset,expect,update);// HotSpot虚拟机层(C++实现)UNSAFE_ENTRY(jboolean,Unsafe_CompareAndSwapInt(...))__cmpxchg(...);// 调用CPU指令UNSAFE_END// 最终生成的汇编指令(x86)lock cmpxchg[内存地址],新值lock前缀的作用很关键:
- 确保对内存的读-改-写操作原子执行
- 禁止指令重排序
- 把写缓冲区中的所有数据刷新到内存中
现代CPU使用缓存一致性协议(如MESI协议)来实现原子操作,而不是简单粗暴地锁住整个总线,这大大提高了性能。
4 CAS在Java中的应用场景
4.1 原子类:AtomicInteger等
Java的java.util.concurrent.atomic包提供了一系列原子类,如AtomicInteger、AtomicLong、AtomicReference等。这些类都是基于CAS实现的。
实战示例:线程安全的计数器
publicclassCASCounter{privateAtomicIntegercount=newAtomicInteger(0);publicvoidincrement(){count.incrementAndGet();}publicintgetCount(){returncount.get();}}4.2 自旋锁实现
基于CAS可以实现一种简单的锁——自旋锁:
publicclassSpinLock{privateAtomicReference<Thread>sign=newAtomicReference<>();publicvoidlock(){Threadcurrent=Thread.currentThread();// 如果锁未被占用,则设置为当前线程占用while(!sign.compareAndSet(null,current)){// 循环等待,直到获取到锁}}publicvoidunlock(){Threadcurrent=Thread.currentThread();sign.compareAndSet(current,null);}}4.3 限流器中的CAS应用
在令牌桶限流器中,CAS可以用于安全地更新令牌数量,如Eureka中的限流器实现。
5 CAS的三大问题及解决方案
尽管CAS很强大,但它并非银弹,也存在一些需要注意的问题。
5.1 ABA问题
问题描述:假设变量X的值为A,线程1准备将A改为C,但在此期间,线程2将A改为B,然后又改回A。这时线程1执行CAS操作时,会错误地认为X的值从未被修改过。
这就像你离开会议室时有一杯水,回来时还有一杯看似相同的水,但可能已经被人喝过又重新倒满了。
解决方案:使用AtomicStampedReference为变量添加版本号:
AtomicStampedReference<Integer>atomicRef=newAtomicStampedReference<>(1,0);// 初始值1,版本号0int[]stampHolder=newint[1];intvalue=atomicRef.get(stampHolder);booleansuccess=atomicRef.compareAndSet(value,2,stampHolder[0],stampHolder[0]+1);5.2 循环时间长导致CPU开销大
问题描述:在高竞争环境下,CAS失败后会不断重试,导致CPU空转,消耗大量计算资源。
解决方案:
- 限制重试次数,超过阈值后采取其他策略
- 使用
Thread.yield()或LockSupport.parkNanos()让出CPU - JVM的自适应自旋优化:根据历史成功率动态调整自旋次数
5.3 只能保证一个变量的原子操作
问题描述:CAS机制只能保证单个变量的原子性,如果需要同时更新多个变量,就无法直接使用CAS。
解决方案:
- 使用锁机制(如
synchronized) - 将多个变量封装成一个对象,使用
AtomicReference:
classPair{intfirst,second;// 构造方法和getter/setter}AtomicReference<Pair>atomicPair=newAtomicReference<>(newPair(1,2));6 CAS与同步锁的性能对比
在高并发环境下,不同的同步机制性能表现各异:
| 实现方式 | 耗时(ms) | 适用场景 |
|---|---|---|
| synchronized | 320 | 高竞争场景,代码简单 |
| ReentrantLock | 280 | 需要高级功能如公平锁、条件变量 |
| CAS | 120 | 低竞争场景,追求高性能 |
注:测试数据为4线程执行100万次操作的情况
推荐使用场景:
- 低竞争环境:使用CAS,避免线程阻塞
- 高竞争环境:使用synchronized或ReentrantLock,减少CPU资源浪费
7 实战经验与最佳实践
7.1 选择合适的并发控制
根据实际场景选择合适并发策略:
- 对于简单的计数器、状态标志,优先考虑原子类
- 对于复杂的复合操作,可能需要使用锁机制
- 考虑使用
LongAdder替代AtomicLong在高并发环境下获得更好性能
7.2 避免常见陷阱
- 不要过度依赖CAS:在极高竞争环境下,CAS可能导致性能下降
- 注意ABA问题:在关键数据上使用带版本号的原子引用
- 合理控制自旋次数:避免无限循环导致CPU资源浪费
8 结语:无锁编程的未来
CAS作为无锁编程的核心技术,是现代高并发应用的重要基石。从Java并发包到数据库实现,从分布式系统到操作系统内核,CAS的思想无处不在。
虽然直接使用Unsafe类存在风险(JDK9+已限制),但通过Java标准库提供的原子类,我们可以安全地享受CAS带来的性能优势。随着硬件技术的发展,无锁编程将在高并发领域发挥越来越重要的作用。
正如计算机科学中许多思想一样,微观的CAS操作反映了宏观的分布式系统设计思想,理解这些底层机制,有助于我们在不同层次上设计出更高效、可靠的系统。
参考资料
- https://blog.csdn.net/zj6182007/article/details/146300371
- https://blog.csdn.net/weixin_42201180/article/details/130714146
- https://blog.csdn.net/ltlt654321/article/details/127238736
- https://blog.csdn.net/m0_63080216/article/details/136203196
- https://blog.csdn.net/weixin_39528219/article/details/114231189
- https://blog.csdn.net/lki_suidongdong/article/details/106036918
- https://www.cnblogs.com/jingzh/p/15576771.html
- https://blog.csdn.net/qq_43001609/article/details/83590911
- https://blog.csdn.net/weixin_51786043/article/details/147445374
本文在技术准确性的基础上,通过比喻和实例力求生动易懂。如果您有任何问题或建议,欢迎在评论区留言讨论。
更多技术干货欢迎关注微信公众号科威舟的AI笔记~
【转载须知】:转载请注明原文出处及作者信息