CAS
原理
CAS, compare and swap的缩写,中文翻译成比较并交换。
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该 位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前 值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”
既然在CAS中存在不断尝试的过程,那么会不会造成很大的资源浪费呢?答案是有可能的,这也是CAS的缺陷之一。但是,既然CAS是一个乐观锁,那么设计者在设计时就应该抱着的是乐观的态度,换句话说,CAS认为自己有非常大的概率是能够成功完成当前操作的,所以在CAS看来,完不成便重试(自旋)是一个小概率事件。
CAS操作,先读再比较,然后设置值,步骤这么多,会不会在步骤之间被其它线程干扰导致冲突?其实是不会的,因为在底层汇编代码中CAS操作并不是用三条指令实现的,而是仅仅是一条指令:lock cmpxchg(x86架构),因此不会出现在CAS执行过程中时间片被抢走的情况。但是这就涉及到另一个问题,CAS操作过分的依赖CPU的设计,也就是说CAS本质是CPU中的一条指令。如果CPU不支持CAS操作,CAS就无法实现。
优点
1.在并发量不是很高时cas机制会提高效率。cas没有锁,也就不会有线程阻塞问题,和线程切换带来的开销。
缺点
-
ABA问题。因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。
从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。 -
循环时间长开销大。自旋CAS如果长时间不成功,不会将线程挂起,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令,那么在效率上会有一定的提升。
-
只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。
适用场景
CAS适合读多写少的场景下使用。