java开发:乐观锁CAS机制

先来说说什么是悲观锁、乐观锁:

悲观锁:总是假设最坏的情况,每次操作数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞,直到它拿到锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

乐观锁:总是假设最好的情况,每次操作数据的时候都认为别人不会修改。所以不会上锁,其他线程依然是可以访问,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。乐观锁一般使用版本号机制和CAS算法实现。

版本号机制和CAS算法实现乐观锁:

版本号机制:给每个共享变量加上version字段,表示数据被修改的次数,当数据被修改时,version值会加1。当线程A操作数据值时,在读取数据时先会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
例如 线程A更新变量value ,value的版本号为1,在操作的时候先获取变量的版本号为1。然后它更新变量value的值,这时候线程B修改了value的值,然后把版本号加1变成了2。当线程A执行操作完成后准备提交之前会先判断数据表的版本号和自己手中的版本号是否相等,它发现自己手中的版本号是1,数据表中的版本号是2,因此它认为别人已经修改过这个变量,撤销了这次提交,并且更新本地value值为最新的值,重新对value进行操作,一直到提交成功

我们知道在java内存模型中所有的共享变量是放在共享内存中,不同的线程都会拷贝这些共享变量到自己的内存中,更新的时候先更新线程本地的值,再push到共享内存当中,读取的时候默认也是先读取本地的。
CAS:CAS有三个参考值
V:变量在共享内存中的值
A:线程本地内存的值
B:线程模拟修改变量后的值
CAS机制在更新一个变量的时候,只有当变量在线程本地内存的值A和共享内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B

举个例子:
1.在共享内存地址V当中,存储着值为10的变量。
在这里插入图片描述

2.此时线程1想要把变量的值增加1,它会先在自己的内存中对变量进行+1操作。对线程1来说,旧的预期值A=10,修改后的新值B=11,共享内存中的实际值是10。

在这里插入图片描述

3.在线程1要提交更新之前,另一个线程2抢先一步,把内存地址V中的变量值率先更新成了11。
在这里插入图片描述

4.线程1开始提交更新,首先进行A和地址V的实际值比较(Compare),发现A不等于V的实际值,提交失败。

在这里插入图片描述
5.线程1重新获取内存地址V的当前值,并重新计算想要修改的新值。此时对线程1来说,A=11,B=12。这个重新尝试的过程被称为自旋操作(先将本地的值更新成共享内存中最新的值,然后重新对变量修改,再提交到共享内存,直到提交成功)。

在这里插入图片描述

6.线程1再次提交,没有其他线程改变地址V的值。线程1进行A和V的比较,发现A和地址V的实际值是相等的。

在这里插入图片描述

7.线程1提交成功,把地址V的值替换为B,也就是12。

在这里插入图片描述

CAS的缺点:

1.CPU开销较大
在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。

2.不能保证代码块的原子性
CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。

参考:
https://blog.csdn.net/wengyupeng/article/details/90239411
https://www.cnblogs.com/myopensource/p/8177074.html

CAS容易引起ABA问题:

什么是ABA问题呢,假设有个共享变量的值为A,此时线程1去修改这个变量,在线程1修改的时候,线程2把这个变量修改成了B,然后线程3又把变量修改成了A。当线程1修改完毕时根据旧的预期值和共享内存的实际值进行比较得到的结果是相等的,则线程1认为变量没被修改过,则线程1提交成功。但其实变量已经被修改,此时的A非彼A。这就是ABA问题。

举个例子:比如说我的卡里有100大洋,此时有俩个线程同时去操作这100大洋,它们拿到的副本都是100。若线程2被阻塞了,线程1执行任务扣掉了50大洋,现在卡里剩50。正好我妈(线程3)此时刚好打给我50大洋,现在卡里的钱又变回了100。当线程2被唤醒后也执行扣款操作,扣掉50大洋,当它准备提交数据时比较旧的预期值和共享内存的实际值发现都是100,因此线程2也扣款成功,卡里只剩50大洋。。。想想就不对劲啊,我本来有100,我妈打给我50,我就取了50。不应该是剩100吗?这不害我白白损失了50大洋吗

解决ABA问题:

当每次修改共享变量提交时,不仅提交新值,还应给变量添加一个版本号或者一个时间戳。每次提交之前先判断旧的预期值是否和共享内存的实际值相等,相等时再比较版本号或者时间戳是否对应。

发布了194 篇原创文章 · 获赞 42 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_39027256/article/details/103688694