深入理解CAS原理!
深入理解CAS原理!
月伴飞鱼
CAS(compareAndSwap)
比较交换,是一种无锁原子算法,映射到操作系统就是一条cmpxchg
硬件汇编指令(保证原子性)。其作用是让
CPU
将内存值更新为新值,但是有个条件,内存值必须与期望值相同。并且
CAS
操作无需用户态与内核态切换,直接在用户态对内存进行读写操作(意味着不会阻塞/线程上下文切换)。
它包含
3
个参数CAS(V,E,N)
,V
表示待更新的内存值,E
表示预期值,N
表示新值。当
V
值等于E
值时,才会将V
值更新成N
值,如果V
值和E
值不等,不做更新,这就是一次CAS
的操作。
CAS如何保证原子性
总线锁定:
指
CPU
使用了总线锁。总线锁就是使用
CPU
提供的LOCK#
信号,当CPU
在总线上输出LOCK#
信号时,其他CPU
的总线请求将被阻塞。
缓存锁定:
总线锁定方式在锁定期间,会导致大量阻塞,增加系统的性能开销。
所谓缓存锁定是指
CPU
对缓存行进行锁定,当缓存行中的共享变量回写到内存时。其他
CPU
会通过总线嗅探机制感知该共享变量是否发生变化,如果发生变化,让自己对应的共享变量缓存行失效。重新从内存读取最新的数据,缓存锁定是基于缓存一致性机制来实现的。
因为缓存一致性机制会阻止两个以上
CPU
同时修改同一个共享变量(现代CPU
基本都支持和使用缓存锁定机制)。
- 缓存行是
CPU
高速缓存存储的最小单位。
CAS的问题
只能保证一个共享变量原子操作:
CAS
只能针对一个共享变量使用,如果多个共享变量就只能使用锁了,也可以把多个变量整成一个变量。
- 也可以利用一个新的类,来整合一组共享变量,利用 AtomicReference 来把这个新对象整体进行 CAS 操作。
自旋时间太长:
当一个线程获取锁时失败,不进行阻塞挂起,而是间隔一段时间再次尝试获取,直到成功为止,这种循环获取的机制被称为自旋。
自旋锁好处:
持有锁的线程在短时间内释放锁,那些等待竞争锁的线程就不需进入阻塞状态(无需线程上下文切换/无需用户态与内核态切换)。
它们只需要等一等(自旋),等到持有锁的线程释放锁之后即可获取,这样就避免了用户态和内核态的切换消耗。
自旋锁坏处:线程在长时间内持有锁,等待竞争锁的线程一直自旋,即CPU一直空转,资源浪费在毫无意义的地方。
ABA问题:
CAS
需要检查待更新的内存值有没有被修改,如果没有则更新。存在这样一种情况,如果一个值原来是
A
,变成了B
,然后又变成了A
,在CAS
检查的时候会发现没有被修改。
- 有两个线程,线程
1
读取到内存值A
,线程1
时间片用完,切换到线程2
,线程2
也读取到了内存值A
。- 并把它修改为
B
值,然后再把B
值还原到A
值,修改次序是A->B->A
。- 接着线程
1
恢复运行,它发现内存值还是A
,然后执行CAS
操作。要解决
ABA
问题只要追加版本号即可,每次改变时加1
。即
A —> B —> A
,变成1A —> 2B —> 3A
,在Java
中提供了AtomicStampedRdference
可以实现这个方案。