前言
最近做新项目,货币充值消耗,送礼竞争勋章等都使用了CAS解决并发问题,所以做一下笔记,谈谈CAS,大家一起互相学习。
乐观锁,悲观锁:
讨论CAS的话,先来说有一下乐观锁,悲观锁。
悲观锁:每次去取数据,很悲观,都觉得会被别人修改,所以在拿数据的时候都会上锁。简言之,共享资源每次都只给一个线程使用,其他线程阻塞,等第一个线程用完后再把资源转让给其他线程。synchronized和ReentranLock等都是悲观锁思想的体现。
乐观锁:每次去取数据,都很乐观,觉得不会被被人修改。因此每次都不上锁,但是在更新的时候,就会看别人有没有在这期间去更新这个数据,如果有更新就重新获取,再进行判断,一直循环,直到拿到没有被修改过的数据。CAS(Compare and Swap 比较并交换)就是乐观锁的一种实现方式。
CAS算法:
CAS涉及三个操作数
1.需要读写的内存地址V
2.进行比较的预期原值A
3.拟写入的新值B
如果内存位置的值V与预期原A值相匹配,那么处理器会自动将该位置值更新为新值B。CAS思想:要进行更新时,认为位置V上的值还是跟A值相等,如果是是相等,就认为它没有被别的线程更改过,即可更新为B值。否则,认为它已经被别的线程修改过,不更新为B的值,返回当前位置V最新的值。
JDK源码中,CAS思想体现:
反编译Unsafe类(用Java Decompiler工具)从源码中可以发现,内部使用自旋的方式进行CAS更新
业务场景以及CAS的应用:
假设多人A,B,C等给D送礼,送总价值最多的那个人,可以成为佩带D的守护皇冠,D的守护皇冠有且只有一个。如果他们同时在给D送礼,送礼价值互相超越,即存在并发问题。
解决思路: 参考乐观锁原理
设置乐观锁失败后尝试次数n次
先查询旧的守护者,即旧的送礼最大价值者。
如果当前旧的守护者不为空,构造当前送礼者为新守护者。
将新的守护者去跟旧的守护者比较送礼的价值,尝试更新数据库。
如果发现更新时,旧的最大送礼价值发生改变了,放弃更新,退出循环,重新尝试(n--)。
如果当前旧的守护者为空,表示以前还没有守护,直接将新的守护插入表。
如果插入表失败,表示在插入过程中,数据被更改了,表明有新的记录抢先成为守护。
那么,重新尝试(n--),直到次数n用完。
抢占守护流程图:
代码实现:
CAS存在的一些问题:
1.ABA问题,
并发环境下,假设初始条件是A,去修改数据时,发现是A就会执行修改。但是看到的虽然是A,中间可能发生了A变B,B又变回A的情况。此时A已经非彼A,数据即使成功修改,也可能有问题。
2.CPU开销
自旋CAS,如果一直循环执行,一直不成功,会给CPU带来非常大的执行开销。所以上面抢占守护的例子,设置了尝试的执行次数n,避免一直循环