CAS
CAS:是 一条 特殊的 CPU 指令,其所做的工作就是 “比较和交换”。
CAS有3个操作数,内存值V,预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。当多个线程同时尝试使用CAS更新一个变量时,任何时候只有一个线程可以更新成功,若更新失败,线程会重新进入循环再次进行尝试。
CAS的巧妙之处:
- 之前的线程安全问题是:两个线程从内存中读取同一个值,导致操作覆盖。
- CAS能确保内存里面的那个值有没有被修改,不变才会进行操作,变了的话就会重新读取内存的值。
CAS应用
1)实现原子类
这是Java标准库提供的基于CAS实现的原子类包
包底下有如下实现的原子类:
红框标注的是在开发中常用的原子类,其作用:就是对某一变量进行 ++
/ --
操作
2)实现自旋锁
CAS实现自旋锁的伪代码:
当 owner
不为 null
的时候,循环就会一直执行下去,通过不断循环来实现 等待 效果
CAS的ABA问题
CAS 在使用的时候,其关键部分就是:判定当前内存中的值是否是和寄存器中的值是一样的;如果是一样的,则进行修改;如果不一样,则不进行修改。
这里的“一样”,本质上是判定当前这个代码执行过程中,是否有其他线程穿插进来将变量给修改了
但是存在这种情况:比如初始时数值为0,在执行CAS之前,另一个线程把这个值从 0 改为了 100,而后又将 100 改为了 0。CAS是无法判断这种情况的
一般情况下是不会有问题的。
但能挑出来说,那就是不出意外的要出意外了:
如:在取钱的场景下,初始情况下账户余额有1000元,现要取500元。但在取钱的时候,ATM卡住了,取钱按钮按第一次没反应,又按了一下。
ATM中就创建了两个线程t1
和t2
来尝试进行扣款操作,该场景下期望其中一个线程操作成功,另一个操作失败。此处假定使用CAS的方法来扣款:
如图:t1
在执行完第一条指令后,t2
穿插了进来提前执行完整个指令,此时余额已经发生了变化,再轮到 t1
执行第二条指令时,因为余额发生了变化,t2
第二条指令就不会执行,直接跳出。在这种场景下,是没有问题的
但如若此时又穿插进来一个 t3
线程,给余额充值了 500
,CAS是无法判断这种情况的,那么余额就会莫名少了 500
解决 ABA 问题方案
- 约定数据变化必须是单向的(只能增加或减少),不可以是双向的(不能既增加又可减少)
- 对于本身就必须双向变化的数据,可以给它引入一个版本号(版本号是只能增加不能减少的)