面试官:你知道CAS,Compare and swap吗

张开发
2026/6/8 8:37:22 15 分钟阅读
面试官:你知道CAS,Compare and swap吗
聊CAS之前得先说说写并发代码最头疼的事——多线程同时改一个共享变量怎么保证线程安全最常用的就是加锁比如synchronized或者Lock但不管哪种锁只要涉及到线程阻塞和唤醒就有性能开销尤其是高并发场景下大量线程抢锁CPU光花在上下文切换上了很不划算。那有没有无锁的线程安全方案呢有这就是CAS。CAS全称Compare-And-Swap翻译一哈就是比较并交换是CPU硬件层面支持的原子操作核心逻辑特别简单就3个核心值要读写的内存地址 V我们预期的旧值 A想要更新成的新值 B它的执行规则只有一条只有当内存地址V里的真实值等于我们预期的旧值A时才会把V的值更新成B否则不执行更新返回当前V的真实值。整个操作是原子的执行中途不会被其他线程打断。上面扯了那么多官话好像也不是太明白那就举个例子吧。举个例子我银行卡里有100块钱内存V100现在要转50块出去预期账户里还是100旧值A100转完之后应该剩50新值B50。这时候CAS就会先检查账户里是不是真的有100块如果是就直接改成50转账成功如果中途被别的线程改了比如已经被转走了30只剩70了和预期的100对不上就不执行转账返回当前的70避免出错。我们平时用的AtomicInteger、AtomicLong这些原子类里面的getAndIncrement()、incrementAndGet()这些方法底层全是靠CAS实现的。它不用加锁不会阻塞线程并发量不极端的情况下性能比重量级锁好太多了。ABA问题CAS虽然香但它不是万能的其中最经典、面试必问的坑就是ABA问题。先说说ABA问题到底是个甚线程1读取到内存V的值是A正要执行CAS更新这时候线程2抢到了CPU把V的值从A改成了B然后又改回了A等线程1回来执行CAS的时候发现V的值还是A和自己预期的一样就顺利完成了更新。但实际上这个A已经不是线程1最开始读到的那个A了中间已经被其他线程改过一轮了只是最终值又变回来了而已。这就是ABA问题。再举个真实的例子我们有一个栈栈顶元素是AA的下一个元素是B现在我们想通过CAS把栈顶换成B也就是弹出A。线程1读取了栈顶的值A预期旧值是A要更新成B这时候线程1被挂起了线程2抢到执行权把A、B全都弹出栈然后又把A重新压回栈里现在栈里只有AB已经不在栈里了线程1恢复执行检查栈顶的值发现还是A和预期的一样CAS执行成功把栈顶换成了B。问题来了B早就被线程2弹出栈了现在栈里根本没有B这个元素线程1的操作直接把整个栈的结构搞崩了。ABA问题的解决方案主流的解决方案就是版本号时间戳机制。具体逻辑也很简单给每个变量额外加一个版本号每次变量被修改版本号就1。CAS操作的时候不仅要对比变量的值还要对比版本号只有两个都和预期的一致才执行更新。还是拿刚才的栈例子来说初始状态栈顶是A版本号是1线程1的预期是值A版本号1要更新成B线程2修改了栈把A弹出又压回这时候A的版本号已经变成3了线程1回来执行CAS发现版本号1和当前的3对不上直接更新失败完美避开了ABA问题。

更多文章