CAS
一、CAS的原理:
CAS中包含了3个操作数——需要读写的内存位置V(内存位置),进行比较的值A(预期值)和拟写入的新值B(新值)。
当且仅当V=A时,CAS才会通过原子方式用新值B来更新V的值,否则不会执行任何操作。并且,无论执行修改与否,都将返回V原有值。且仅且只有一个线程能更新变量的值,而其它线程都将失败。然而,失败的线程并不会被挂起(这与获取锁的情况不同:获取锁失败时线程会挂起或自旋),而是被告知在这次竞争中失败,并可以再次重试。
CAS的优势:
由于竞争CAS时失败并不会阻塞,因此它可以决定是否重新尝试、或者执行一些恢复操作、也或者不执行任何操作(这是一种明智的做法)。这种灵活性大大减少了与锁先相关的活跃性危险(尽管在一些不常见的情况下仍然存在活锁风险)。
CAS的典型使用模式:
首先从V中读取值预期值A,并根据A计算新值B,然后通过CAS以原子方式将V中的值由A变成B(只要在这期间没有任何线程将V的值修改为其它值)。
由于CAS能检测到来自其它线程的干扰,因此即使不使用锁(无锁)也能够实现原子的读-写-改操作序列。
二、JVM对CAS的支持
JVM是如何确保处理器执行CAS操作的呢?Java5.0之前,如果不编写明确的代码,那么就无法执行CAS。在Java5.0之后引入了底层的支持,在int,long和对象的引用等类型上都公开了CAS类型,并且JVM把它们编译为底层硬件支持的最有效的方法。在支持CAS的平台上,运行时把它们编译为相应的(额外)机器指令。在最坏的条件下,如果不支持CAS等指令,那么JVM将会使用自旋锁(spin lock)。
三、ABA问题
在CAS操作中判断“V的值是否(依然)为A”,如果是则执行更新操作。但是问题来了,如果这里的A是经过其它线程更改为B,然后又改回为A,会有什么影响呢?
如果A仅仅代表一个普通数值,其改变并不会受什么影响。但如果A是一个引用地址,则会出现地址重用的问题(地址被重用是很经常发生的,一个内存分配后释放了,再分配,很有可能还是原来的地址)
举个简单的例子:一个骗子与人交易,趁买家不注意用一个外表一模一样的假iphone把原来的iphone调包了。买家拿到手机后,高高兴兴的回家了。
解决方案是增加一个版本号来控制,每次改动就会让版本号加1,即使A经过改变后又变回A,但版本号可以说明其已经发生变化了。这种乐观控制的思想其实用的很多的,比如Hibernate中就用到过,还有集合中的fast-fail快速失败机制也是用版本控制的思想来实现。
实际上AtomicMarkableReference和AtomicStampedReference就是使用类似方法从而避免ABA问题的。
面试题
1.什么是CAS?其原理、优势?
2.JVM如何支持CAS的?
3.CAS的缺陷?
①自旋时间太长,会浪费cpu资源。
②CAS只能保证单个变量原子性
③ABA问题
4.什么是ABA问题?如果解决?
参考资料:《Java并发编程实战》