浅谈CAS
前言:前文讨论了volatile保证工作内存和主内存之间的数据一致性问题(可见性),但是运算的原子性没有保证,那么使用CAS就可以用来解决这个原子性运算问题。
CAS,即Compare And Swap,是一种典型乐观锁的实现。来自大佬的定义:CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B。CAS指令执行时,当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。整个比较并替换的操作是一个原子操作。Java中有自带的原子类数据类型,例如AtomicInteger,调用unsafe包实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | public class CASDemo { public static AtomicInteger i = new AtomicInteger( 0 ); private static final int THREADS_COUNT = 20 ; private static void inCrease() { i.incrementAndGet(); } public static void main(String[] args) throws InterruptedException { Thread[] threads = new Thread[THREADS_COUNT]; CountDownLatch countDownLatch = new CountDownLatch(THREADS_COUNT); for ( int j = 0 ; j < THREADS_COUNT; j++) { threads[j] = new Thread( new Runnable() { @Override public void run() { for ( int k = 0 ; k < 10000 ; k++) { inCrease(); } countDownLatch.countDown(); } }); threads[j].start(); } countDownLatch.await(); System.out.println(i); } } |
此时输出结果为200000,为线程安全。
unsafe的cas操作在实现上也是在汇编层面lock加锁,所以同时具备volatile的性质:可见性和禁止指令重排序。
CAS也有它的问题,一个是循环开销问题,CAS经常和循环一起使用,执行失败就会重试,如果一个变量的并发操作量很大的时候,这时循环执行就有大量失败和重试次数,造成过大的Cpu开销。
二是ABA问题,CAS只是关心Value和ExpectedValue是否相等,相等则继续执行,那么如果执行期间这个变量被其他线程使用过,但是结果不变,这时就CAS就会误认为该值没有变化过。Java引入了“AtomicStampedReference”,通过同时比较Value的值和版本控制解决了这个问题,MySQL的MVCC(多版本并发控制)也是采取了类似的设计思想。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~