1、CAS简介
jvm的syncronized的重量级锁设计操作系统内核态下互斥锁的使用,线程阻塞和唤醒都涉及到进程在用户态和内核态的频繁切换,导致重量级锁开销大性能低。而syncronized轻量级锁使用CAS进行自旋抢锁,CAS是cpu指令级的原子操作,并处于用户态下,开销较小。
Compare-And-Swap,中文叫做“比较并交换”,它是一种思想、一种算法。
操作系统层面的CAS是一条CPU原子指令(cmpxchg指令),java5所增加的JUC并发包中对操作系统的底层CAS原子操作进行了封装,为上层的java程序提供了CAS操作的API。Unsafe(sun.misc.Unsafe)提供的CAS方法直接通过native方式(封装c++代码)调用了底层的CPU指令cmpxchg。
CAS有三个操作参数:内存值V,预估值A,要修改的值B,当且仅当预期值A和内存值V相同时,才将内存值修改为B,否则什么都不做。CAS 的特点是避免使用互斥锁,当多个线程同时使用 CAS 更新同一个变量时,只有其中一个线程能够操作成功,而其他线程都会更新失败。不过和同步互斥锁不同的是,更新失败的线程并不会被阻塞,而是被告知这次由于竞争而导致的操作失败,但还可以再次尝试。
cmpxchg如何保证多核心下的线程安全?
系统底层进行CAS操作时,会判断当前系统是否为多核心系统,如果是就给总线加锁,只有一个线程会对总线加锁成功,加锁成功后会执行CAS操作,CAS的原子性是平台级别的。
2、Unsafe类介绍
sun.misc.Unsafe是一个final修饰的类,并且构造方法私有
通过反射获取Unsafe对象
public static Unsafe getUnsafeInstance(){try {final Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");theUnsafe.setAccessible(true);return (Unsafe) theUnsafe.get(null);} catch (NoSuchFieldException | IllegalAccessException e) {e.printStackTrace();}return null;}
Unsafe类CAS相关方法:
/***@param o 需要操作字段所在对象*@param offset 需要操作字段相对于对象头的偏移量*@param expected 期望值(旧值)*@param x 更新值(新值)*/public final boolean compareAndSwapObject(Object o, long offset,Object expected,Object x)public final boolean compareAndSwapInt(Object o, long offset,int expected,int x)public final boolean compareAndSwapLong(Object o, long offset,long expected,long x)
使用Unsafe CAS方法:
public class Test {static Unsafe U;int count;static Long countOffset;static {try {final Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");theUnsafe.setAccessible(true);U = (Unsafe) theUnsafe.get(null);countOffset = U.objectFieldOffset(FieldVisibility.class.getDeclaredField("count"));} catch (Exception e) {log.error("Unsafe 初始化异常",e);}}protected final boolean compareAndSetCount(Integer expectedValue, Integer newValue) {return U.compareAndSwapInt(this, countOffset, expectedValue, newValue);}}
3、 原理分析
Unsafe是CAS核心类。Java无法访问底层操作系统,而是通过本地(native)方法访问,Unsafe提供了硬件级别的原子操作。
- AtomicInteger加载Unsafe工具用来直接操作内存
 - 用Unsafe来实现底层操作
 - 用volatile修饰value字段,保证可见性
 - Unsafe类中的compareAndSwapInt方法
 - 方法中先想办法拿到变量value在内存中的地址
 - 通过Atomic::cmpxchg实现原子性的比较和替换,其中参数x是即将更新的值,参数e是原内存的值。
static {try {valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));} catch (Exception ex) { throw new Error(ex); }}
 
public final int getAndSetInt(Object var1, long var2, int var4) {int var5;do {var5 = this.getIntVolatile(var1, var2);} while(!this.compareAndSwapInt(var1, var2, var5, var4));return var5;}
4、CAS的缺点
- ABA问题
 
线程1将结果改为B线程2将结果改为A,让线程误以为没有线程对数据进行操作
解决方案: 加版本号,使用AtomicStampedReference
public class AtomicStampedReference<V> {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);}}private volatile Pair<V> pair;/***expectedReference 期望引用(旧值)*newReference 新引用(旧值)*expectedStamp 期望版本号(旧值)*newStamp 新版本号*/public boolean compareAndSet(V expectedReference,V newReference,int expectedStamp,int newStamp) {Pair<V> current = pair;returnexpectedReference == current.reference &&expectedStamp == current.stamp &&((newReference == current.reference &&newStamp == current.stamp) ||casPair(current, Pair.of(newReference, newStamp)));}}
- 高并发时的性能问题,自旋时间比较长
 
由于单次 CAS 不一定能执行成功,所以CAS 往往是配合着循环来实现的,有的时候甚至是死循环,不停地进行重试,直到线程竞争不激烈的时候,才能修改成功。如果应用场景本身就是高并发的场景,就有可能导致 CAS 一直都操作不成功,循环时间就会越来越长。在此期间,CPU 资源也是一直在被消耗,这会对性能产生很大的影响。
- 范围不能灵活控制
 
不能针对多个共享变量同时进行 CAS 操作,因为这多个变量之间是独立的,简单的把原子操作组合到一起,并不具 备原子性。因此如果我们想对多个对象同时进行 CAS 操作并想保证线程安全的话,是比较困难的
