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的变量。
java开发:乐观锁CAS机制

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

java开发:乐观锁CAS机制

3.在线程1要提交更新之前,另一个线程2抢先一步,把内存地址V中的变量值率先更新成了11。
java开发:乐观锁CAS机制

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

java开发:乐观锁CAS机制
5.线程1重新获取内存地址V的当前值,并重新计算想要修改的新值。此时对线程1来说,A=11,B=12。这个重新尝试的过程被称为自旋操作(先将本地的值更新成共享内存中最新的值,然后重新对变量修改,再提交到共享内存,直到提交成功)。

java开发:乐观锁CAS机制

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

java开发:乐观锁CAS机制

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

java开发:乐观锁CAS机制

CAS的缺点:

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

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

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