CAS是Java多线程开发中一个基础概念,包括很多其他的中间件都会用到它。那么我们就从原理到应用,一起去揭开它的面纱。
一、CAS原理
CAS,Compare and Swap,比较并交换。是通过原子指令的方式实现在多线程下的同步功能。
在实现过程中,通过把前后两次获取到的变量在内存中的值进行比对,如果相等则对变量值进行更改,否则重新读取内存的值,然后重复上述过程。
在Java中,CAS是属于乐观锁的一种,也就是那种不进行加锁,而是假定在没有冲突的条件下去完成一个操作,如果中途发生冲突(例如变量的内存值被改动过,导致V和A值不相等等情况)导致失败后马上进行重试,直到成功。
既然有乐观锁,那么与之对应的就是悲观锁。synchronized 就是悲观锁的一种,这种会对作用范围内的资源进行锁定,其他线程需要等待所释放才能获取这把锁。
二、CAS的不足
虽然说CAS的使用广泛,但是其会出现三个主要的问题:
1、ABA问题
在做CAS更新操作中,读取到变量的值A,在准备为变量赋值的时候期望值依旧为A,但是变量值可能已被多次修改后充值改回A,这个CAS的更新漏洞就被叫做ABA。
2、循环时间长开销大
自旋CAS的方式如果长时间不成功,会给CPU带来很大的开销。
3、只能保证一个共享变量的原子操作
只对一个共享变量操作可以保证原子性,但是多个则不行。
三、CAS在Java中的应用
Java中大量使用CAS,比较集中在java.util.concurrent包内。
为了解决ABA问题,Java提供多个解决方案
1、AtomicStampedReference
package multithread.cas; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicStampedReference; public class AtomicReferenceTest { public static void main(String[] args) { boolean result = CasUtil.increment(); System.out.println("result:"+result); result = CasUtil.increment(); System.out.println("result:"+result); } public static class CasUtil { public static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference(1,1); //自增操作 public static boolean increment() { boolean result = atomicStampedReference .compareAndSet(atomicStampedReference.getReference(), //expectedReference,引用的期望值 atomicStampedReference.getReference()+1, atomicStampedReference.getStamp(),//expectedStamp,版本戳的期望值 atomicStampedReference.getStamp()+1); while(!result) { System.out.println("result is false"); return increment(); } return true; } } }设置atomicStampedReference.getReference()和atomicStampedReference.getStamp()这两条语句保证了期望值和原值必然一致,所以main方法打印结果一直是“true”。
接下来我们分析代码,进入到AtomicStampedReference类的compareAndSet方法,其获取内存值与传入的期待值比较并返回比较结果。在前三个比较项如有一项不成立(一般是第三项),进行第四个条件操作即调用casPair方法
casPair方法,调用UNSAFE.compareAndSwapObject。
private static final long pairOffset = objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class); /** * Atomically sets the value of both the reference and stamp * to the given update values if the * current reference is {@code ==} to the expected reference * and the current stamp is equal to the expected stamp. * * @param expectedReference the expected value of the reference * @param newReference the new value for the reference * @param expectedStamp the expected value of the stamp * @param newStamp the new value for the stamp * @return {@code true} if successful */ public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair<V> current = pair; return expectedReference == current.reference && //判断旧引用,和当前的是否一样;判断 expectedStamp == current.stamp && //旧版本,和当前的是否一样 ((newReference == current.reference &&newStamp == current.stamp) || //判断引用和版本是否都相等,也就是替换和被替换的是同一个对象就不需要再调用casPair了 casPair(current, Pair.of(newReference, newStamp))); } private boolean casPair(Pair<V> cmp, Pair<V> val) { return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);//调用UNSAFE的compareAndSwapObject方法进行对象比较及修改 } private static class Pair<T> { final T reference; final int stamp; private Pair(T reference, int stamp) { this.reference = reference; this.stamp = stamp; } static <T> Pair<T> of(T reference, int stamp) { return new Pair<T>(reference, stamp); //每次返回一个新Pair对象 } } static long objectFieldOffset(sun.misc.Unsafe UNSAFE, String field, Class<?> klazz) { try { return UNSAFE.objectFieldOffset(klazz.getDeclaredField(field)); } catch (NoSuchFieldException e) { // Convert Exception to corresponding Error NoSuchFieldError error = new NoSuchFieldError(field); error.initCause(e); throw error; } }UNSAFE类的代码
public native long objectFieldOffset(Field var1); public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);