深入理解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可以实现这个方案。















