什么是CAS
CAS( Compare-And-Swap)”比较并交换”(乐观锁的原理)
CAS 的特点是避免使用互斥锁,当多个线程同时使用 CAS 更新同一个变量时,只有其中一个线程能够操作成功,而其他线程都会更新失败。更新失败的线程并不会被阻塞,而是被告知这次由于竞争而导致的操作失败,但还可以再次尝试。
CAS 的思路
CAS 相关的指令是具备原子性的
CAS 有三个操作数:内存值 V、预期值 A、要修改的值 B。
核心思路:仅当预期值 A 和当前的内存值 V 相同时,才将内存值修改为 B。
下面我们用图解和例子的方式,让 CAS 的过程变得更加清晰,如下图所示:
假设有两个线程,分别使用两个 CPU,它们都想利用 CAS 来改变右边的变量的值。我们先来看线程 1,它使用 CPU 1,假设它先执行,它期望当前的值是 100,并且想将其改成 150。在执行的时候,它会去检查当前的值是不是 100,发现真的是 100,所以可以改动成功,而当改完之后,右边的值就会从 100 变成 150。
如上图所示,假设现在才刚刚轮到线程 2 所使用的 CPU 2 来执行,它想要把这个值从 100 改成 200,所以它也希望当前值是 100,可实际上当前值是 150,所以它会发现当前值不是自己期望的值,所以并不会真正的去继续把 100 改成 200,也就是说整个操作都是没有效果的,此次没有修改成功,CAS 操作失败。
当然,接下来线程 2 还可以有其他的操作,这需要根据业务需求来决定,比如重试、报错或者干脆跳过执行。举一个例子,在秒杀场景下,多个线程同时执行秒杀,只要有一个执行成功就够了,剩下的线程当发现自己 CAS 失败了,其实说明兄弟线程执行成功了,也就没有必要继续执行了,这就是跳过操作。所以业务逻辑不同,就会有不同的处理方法,但无论后续怎么处理,之前的那一次 CAS 操作是已经失败了的。
CAS 和乐观锁的关系,什么时候会用到 CAS?
CAS 在并发容器中的使用情况
1、ConcurrentHashMap中的casTabAt方法
2、ConcurrentLinkedQueue(非阻塞并发队列)的 offer 方法
CAS 在数据库中的使用情况
利用 version 字段在数据库中实现乐观锁和 CAS 操作,而在获取和修改数据时都不需要加悲观锁。
CAS 在原子类中的使用情况
…
CAS 有什么缺点?
1、ABA 问题。
假设第一个线程拿到的初始值是 100,然后进行计算,在计算的过程中,有第二个线程把初始值改为了 200,然后紧接着又有第三个线程把 200 改回了 100。等到第一个线程计算完毕去执行 CAS 的时候,它会比较当前的值是不是等于最开始拿到的初始值 100,此时会发现确实是等于 100,所以线程一就认为在此期间值没有被修改过,就理所当然的把这个 100 改成刚刚计算出来的新值,但实际上,在此过程中已经有其他线程把这个值修改过了,这样就会发生 ABA 问题。
添加一个版本号 , A→B→A 变成了 1A→2B→3A,这样一来,就可以通过对比版本号来判断值是否变化过,解决 ABA 的问题。
2、自旋时间过长
3、范围不能灵活控制