CAS

悲观锁 VS 乐观锁

在多线程并发编程的环境下,共享变量的更新是一门艺术,很容易就造成数据不一致

根据 JMM 可知,每个线程都有自己的工作内存且相互独立,保存着主内存的副本。每次线程需要读写数据时,需要去主内存拷贝一份到自己的工作内存中

假设主内存有一个共享变量x = 1

此时主内存中变量x的值为 2,但是两个线程都对它 +1,正确的结果应该是 3 才对,这就是并发问题

目前对共享变量的更新采用两种模式:

悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确

乐观锁适合读操作多的场景,不加锁可以提高读操作的性能

CAS 是什么

CAS (Compare And Swap):比较并替换,它有三个核心参数:共享变量的内存地址预期原值新值

每次去指定内存地址更新共享变量值之前先判断是否和预期原值一致,如果一致就直接更新,否则就不做任何处理。所以往往 CAS 都配合失败重试使用

注意:就 CAS 更新这一操作来说,它是一个原子性操作,不会造成数据不一致性

CAS 缺点

ABA 问题

预期原值:A;新值:C

如果共享变量的值被其它线程修改为 B,那么此时 CAS 就会更新失败,然后不断重试。若在重试阶段有其它线程将共享变量的值改回了 A,那么就会被 CAS 更新成功

共享变量的值经历了 A -> B -> A 的过程,虽然最后的值回到了 A,但却还是被修改过。所以为了解决这种问题,加入了版本的概念:1A -> 2B -> 3A

现在判断是否和预期原值相等就必须判断值和版本是否均相等才可以被更新

可能会消耗较高的 CPU

虽然 CAS 没有使用锁,线程从阻塞机制变成了非阻塞机制,但是在线程竞争比较大的时候,如果 CAS 不能更新成功,就会一直重试,消耗 CPU 资源

不能保证代码块的原子性

CAS 只能保证一个共享变量更新的原子性,对于代码块的原子性依旧不能保证,这个时候就需要用到 synchronized 了 (万能!!)

CAS 优点

Demo

参考文章