文章目录
如果不理解CAS操作,可以看看我之前写的博客----CAS操作的基本原理
1、什么是ABA问题
CAS中的关键,就是先用内存中的值和旧的预期值进行比较,如果相同,则交换内存中的值和新值。如果不相等,则什么都不做。
比较其实就判断内存中的值是否被改变,如果相同,则认为没发生改变。但是这种结论是存在一定的漏洞的。因为内存中的值和旧值相同,可能确实没有发送改变,但也有可能改变了,最终又变回来了。
例如:
- 进程P1在共享变量中读到值为A
- P1被抢占了,进程P2执行
- P2把共享变量里的值从A改成了B,再改回到A,此时被P1抢占。
- P1回来看到共享变量里的值没有被改变,于是继续执行。
这样的漏洞,在大多数情况下,其实也没啥影响,但在极端情况下也会引起BUG
ABA问题好比我今天去买了个手机,我拿到这个手机,我无法区分,它是一个新机(出厂到现在一直没有使用过),还是一个翻新机(出厂之后已经卖给别人了,使用一段时间后,旧了,被JS回收回来,换了一个壳,当做新机来卖),虽然我拿到的有可能是一个翻新机,但翻新机大多情况下也是能用的,也挺好用的,但是存在少数情况下还是会翻车
举一个经典的例子,ABA问题产生的BUG
冯同学的账户有100,他要去取50。当按下取款的操作的时候,机器卡了一下,冯同学多按了一下取款。这就相当于,一次取钱操作,执行了两遍(两个线程,并发的去执行这个取款操作)。冯同学的预期结果应该是只取成功一次,也就是说希望取走50,账户还剩50。
如果基于CAS的方式来实现这里的取款:
按照上述的分析,此处就是两个操作,实际只成功一次。此时还没有引入ABA问题。
引入ABA问题
这里存在两个巧合,导致了ABA问题。
1.一次取钱操作,执行了两边
2.第二次取款前的一瞬间,冯同学的朋友给他转了50
虽然这样的极端的场景出现的概率非常低,作为一个合格的程序员,我们也要进行处理。
2、解决ABA问题
解决ABA问题的思路就是引入一个版本号或者时间戳,这里我们用版本号,这个版本号只能变大, 不能变小,修改变量的时候,比较的就不是变量本身,而是比较版本号
此处要求,每次针对余额进行修改,都让版本号加1,每次修改之前,也要先对比版本号,看看旧版本和当前版本是否一致,如果不一致,则放弃操作。
- 最开始,CPU1和CPU2以及内存中的版本号都为1。
- 当t1操作成功后,内存中的版本号为2。
- 冯同学的朋友转账50,内存中的版本号为3。
- t2操作,虽然变量值相同,但版本号不同(一个为1,一个为3),则放弃操作。