如果要深入了解具体实现:CAS原理(以AtomicInteger为例)

导入

synchronized这样的锁是悲观锁,始终假定会发生并发冲突,会屏蔽一切可能违反数据完整性的约束

乐观锁假设不会发生并发冲突,所以它只会在提交时检查是否保证数据完整性,如果提交失败则会进行重试,而最常见的就是CAS(Compare and Swap)了


CAS

一种高效实现线程安全性的方法

    支持原子更新操作,适用于计数器,序列发生器(就是给变量自增的工具)等场景

    属于乐观锁机制,号称lock-free(底层也是有加锁行为的)

    CAS操作失败是,由开发者决定是否继续尝试,还是其他操作,因此争用CAS失败的线程不会被阻塞挂起


CAS思想

包含三个操作数——内存位置(V)预期原值(A)新值(B):当一个线程需要修改一个共享变量的值,完成这个操作需要先取出共享变量的值,赋给A基于A进行计算,得到新值B,在用预期原值A和内存中的共享变量值进行比较(这可以通过一个简单的布尔响应(这个变体通常称为比较和设置),或通过返回从内存位置读取的值来完成),如果相同就认为其他线程没有进行修改,而将新值写入内存


JAVA中CAS的实现

JAVA1.5开始引入了CAS,主要代码都放在JUC的atomic包下

分析其中一个,比如AtomicInteger,它使用到了Unsafe这个类


CAS的缺点

CPU开销比较大:在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,又因为自旋的时候会一直占用CPU,如果CAS一直更新不成功就会一直占用,造成CPU的浪费。

 

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

 

ABA问题:线程1从内存X中取出A,这时候另一个线程2也从内存X中取出A,并且线程2进行了一些操作将内存X中的值变成了B,然后线程2又将内存X中的数据变成A,这时候线程1进行CAS操作发现内存X中仍然是A,然后线程1操作成功。虽然线程1的CAS操作成功,但是整个过程就是有问题的。


CAS引起的ABA问题

比如说我要取100,但是由于机器故障开启了两次取钱的线程,由于采用的是CAS操作,线程一如果执行成功,而线程二由于某些原因阻塞了,并且在这个阶段中有其他用户向我的账户中转了50元,并且该线程执行成功,那么线程二恢复的时候,会发现地址的值V=100,线程二比较V、A发现相同,就更新余额为50.这样就发生错误。


CAS的ABA问题解决方案

不是只是更新某个引用的值, 而是更新两个值,包含一个引用和一个版本号

AtomicStampedReference以及AtomicMarkableReference支持在两个变量上执行原子的条件更新。

AtomicStampedReference将更新一个“对象引用——版本号”二元组,通过在引用上加上“版本号”,从而避免ABA问题(reference——stamp

AtomicMarkableReference将更新一个“对象引用——布尔值”二元组,在某些算法中将通过这种二元组使节点保存在链表中同时又将其标记为“已删除节点”(reference——mark