背景介绍:CAS 完整名称为 : “Compare and Swap” 比较并替换。
里面涉及三个基本操作数,内存地址值:V;旧的预期值:A;新的预期值:B。
规则:当更新变量时,只有当内存地址里的值V = 旧的预期值A,才能被成功的更新为B。
举个例子:
在内存地址V中,存在一个变量值为10的值:
此时线程一,线程二 ,同时去操作这个变量,例如都想+1;
对于线程一来说: A:10; B: 11; V: 10
但是由于线程二比线程一执行的更快,此时线程二已经把内存地址V中的10更新为11.
那么当线程一再次执行更新操作,发现 V = 11,A = 10, V≠A,更新失败。
此时线程一需要重新读取内存中的值,并重新计算要更新的新值,计算后得出:
A= 11,B= 12. 重新提交请求,这个过程称为自旋。
首先Compare
V== A, 然后进行swap
, V = 12,被更新为12.
从思想上来说,syncronized
属于悲观锁,认为并发情况非常严重,CAS
机制属于乐观锁,认为并发情况并不是一种特别严重的情况,发生问题,不断尝试更新即可。
使用场景:Atomic系列类,以及Lock系列类的底层实现;不适合高并发的场景,高并发还是
syncronized
缺点
CPU消耗大
如果在多个线程并发去更新一个变量,多个线程同时去重复尝试更新某一个变量,却又一直不成功,循环往复就会给CPU造成很大的压力。
不能保证代码块的原子性
CAS 可以保证一个变量的原子性,却无法保证整个代码块的原子性。
ABA问题—–最大的问题
ABA 问题
举例说明:
假设有三个线程都要对内存地址V 中的 A 进行操作,
线程一:旧的预期值:A;新的预期值:B
线程二:旧的预期值:A;新的预期值:B
线程三:还未开始工作
- 线程一首先进行了操作,顺利将A更新为B:
此时线程三上来了,它是:旧的预期值:B;新的预期值:A,此时发现:
V(B) == B(B),更新成功,变为图下:
此时线程二开始工作,线程二的目标是:旧的预期值:A;新的预期值:B,同样一比对,相同,于是也更新成功,变为如下:
虽然从结果上来看,A 成功的变为 B,然后在实际涉及钱的时候,问题就大了。
再次举例:
小明银行卡里有200块钱,想取出100块钱,但是由于设备故障,提交了两次扣款申请。
扣款申请1:旧的预估值:200,新的预估值:100
扣款申请2:旧的预估值:200,新的预估值:100
此时,扣款申请1提前进行了操作,扣款成功,小明银行卡里为100块钱;
扣款申请2假设还在等待。//block
此时小明的妈妈给小明存储100元,ok, 现在小明银行卡里就是200块钱。
扣款申请2恢复正常,开始必对,旧的预估值:200,卡里余额200,成功,替换更新为100. 更新成功,那么此时小明银行卡里剩余金额:100块。
哈哈!!!这显然是不对的,虽然值相同,但是中间操作改变了最终的结果。那如何解决这个问题呢?可以利用版本号,每修改成功一次V,都要设置一个版本号。每次更新的时候,不但要比对值还要比对版本号,就可以避免这种错误。
好,就上面的例子说明下:
当扣款申请1完成更新操作时,我们设置内存地址V中的值的版本号为::01
第二次操作:妈妈成功向卡里存入了100块
扣款申请2开始工作:
旧的预估值:200,新的预估值:100;版本号为00
而此时:V(200)= 旧的预估值(200)成立
版本号(00) ≠ version(02) 不成立