CAS机制

背景介绍:CAS 完整名称为 : “Compare and Swap” 比较并替换。

里面涉及三个基本操作数,内存地址值:V;旧的预期值:A;新的预期值:B。

规则:当更新变量时,只有当内存地址里的值V = 旧的预期值A,才能被成功的更新为B。

举个例子:

  • 在内存地址V中,存在一个变量值为10的值:

    image.png

  • 此时线程一,线程二 ,同时去操作这个变量,例如都想+1;

    image.png

    对于线程一来说: A:10; B: 11; V: 10

    但是由于线程二比线程一执行的更快,此时线程二已经把内存地址V中的10更新为11.

​ 那么当线程一再次执行更新操作,发现 V = 11,A = 10, V≠A,更新失败。

​ 此时线程一需要重新读取内存中的值,并重新计算要更新的新值,计算后得出:

​ A= 11,B= 12. 重新提交请求,这个过程称为自旋。

image.png

​ 首先Compare V== A, 然后进行swap , V = 12,被更新为12.

从思想上来说,syncronized 属于悲观锁,认为并发情况非常严重,CAS 机制属于乐观锁,认为并发情况并不是一种特别严重的情况,发生问题,不断尝试更新即可。

使用场景:Atomic系列类,以及Lock系列类的底层实现;不适合高并发的场景,高并发还是 syncronized

缺点

  • CPU消耗大

    如果在多个线程并发去更新一个变量,多个线程同时去重复尝试更新某一个变量,却又一直不成功,循环往复就会给CPU造成很大的压力。

  • 不能保证代码块的原子性

    CAS 可以保证一个变量的原子性,却无法保证整个代码块的原子性。

  • ABA问题—–最大的问题

ABA 问题

举例说明:

假设有三个线程都要对内存地址V 中的 A 进行操作,

image.png

线程一:旧的预期值:A;新的预期值:B

线程二:旧的预期值:A;新的预期值:B

线程三:还未开始工作

  1. 线程一首先进行了操作,顺利将A更新为B:
    image.png

此时线程三上来了,它是:旧的预期值:B;新的预期值:A,此时发现:

image.png

​ V(B) == B(B),更新成功,变为图下:

image.png

此时线程二开始工作,线程二的目标是:旧的预期值:A;新的预期值:B,同样一比对,相同,于是也更新成功,变为如下:

image.png

虽然从结果上来看,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

image.png

第二次操作:妈妈成功向卡里存入了100块

image.png

扣款申请2开始工作:

旧的预估值:200,新的预估值:100;版本号为00

而此时:V(200)= 旧的预估值(200)成立

​ 版本号(00) ≠ version(02) 不成立

-------------本文结束感谢您的阅读-------------