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对象

  1. public static Unsafe getUnsafeInstance(){
  2. try {
  3. final Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
  4. theUnsafe.setAccessible(true);
  5. return (Unsafe) theUnsafe.get(null);
  6. } catch (NoSuchFieldException | IllegalAccessException e) {
  7. e.printStackTrace();
  8. }
  9. return null;
  10. }

Unsafe类CAS相关方法:

  1. /**
  2. *@param o 需要操作字段所在对象
  3. *@param offset 需要操作字段相对于对象头的偏移量
  4. *@param expected 期望值(旧值)
  5. *@param x 更新值(新值)
  6. */
  7. public final boolean compareAndSwapObject(Object o, long offset,Object expected,Object x)
  8. public final boolean compareAndSwapInt(Object o, long offset,int expected,int x)
  9. public final boolean compareAndSwapLong(Object o, long offset,long expected,long x)

使用Unsafe CAS方法:

  1. public class Test {
  2. static Unsafe U;
  3. int count;
  4. static Long countOffset;
  5. static {
  6. try {
  7. final Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
  8. theUnsafe.setAccessible(true);
  9. U = (Unsafe) theUnsafe.get(null);
  10. countOffset = U.objectFieldOffset(FieldVisibility.class.getDeclaredField("count"));
  11. } catch (Exception e) {
  12. log.error("Unsafe 初始化异常",e);
  13. }
  14. }
  15. protected final boolean compareAndSetCount(Integer expectedValue, Integer newValue) {
  16. return U.compareAndSwapInt(this, countOffset, expectedValue, newValue);
  17. }
  18. }

3、 原理分析

Unsafe是CAS核心类。Java无法访问底层操作系统,而是通过本地(native)方法访问,Unsafe提供了硬件级别的原子操作。

  • AtomicInteger加载Unsafe工具用来直接操作内存
  • 用Unsafe来实现底层操作
  • 用volatile修饰value字段,保证可见性
  • Unsafe类中的compareAndSwapInt方法
  • 方法中先想办法拿到变量value在内存中的地址
  • 通过Atomic::cmpxchg实现原子性的比较和替换,其中参数x是即将更新的值,参数e是原内存的值。
    1. static {
    2. try {
    3. valueOffset = unsafe.objectFieldOffset
    4. (AtomicInteger.class.getDeclaredField("value"));
    5. } catch (Exception ex) { throw new Error(ex); }
    6. }
  1. public final int getAndSetInt(Object var1, long var2, int var4) {
  2. int var5;
  3. do {
  4. var5 = this.getIntVolatile(var1, var2);
  5. } while(!this.compareAndSwapInt(var1, var2, var5, var4));
  6. return var5;
  7. }

4、CAS的缺点

  • ABA问题

线程1将结果改为B线程2将结果改为A,让线程误以为没有线程对数据进行操作
解决方案: 加版本号,使用AtomicStampedReference

  1. public class AtomicStampedReference<V> {
  2. private static class Pair<T> {
  3. final T reference;
  4. /**版本号*/
  5. final int stamp;
  6. private Pair(T reference, int stamp) {
  7. this.reference = reference;
  8. this.stamp = stamp;
  9. }
  10. static <T> Pair<T> of(T reference, int stamp) {
  11. return new Pair<T>(reference, stamp);
  12. }
  13. }
  14. private volatile Pair<V> pair;
  15. /**
  16. *expectedReference 期望引用(旧值)
  17. *newReference 新引用(旧值)
  18. *expectedStamp 期望版本号(旧值)
  19. *newStamp 新版本号
  20. */
  21. public boolean compareAndSet(V expectedReference,
  22. V newReference,
  23. int expectedStamp,
  24. int newStamp) {
  25. Pair<V> current = pair;
  26. return
  27. expectedReference == current.reference &&
  28. expectedStamp == current.stamp &&
  29. ((newReference == current.reference &&
  30. newStamp == current.stamp) ||
  31. casPair(current, Pair.of(newReference, newStamp)));
  32. }
  33. }
  • 高并发时的性能问题,自旋时间比较长

由于单次 CAS 不一定能执行成功,所以CAS 往往是配合着循环来实现的,有的时候甚至是死循环,不停地进行重试,直到线程竞争不激烈的时候,才能修改成功。如果应用场景本身就是高并发的场景,就有可能导致 CAS 一直都操作不成功,循环时间就会越来越长。在此期间,CPU 资源也是一直在被消耗,这会对性能产生很大的影响。

  • 范围不能灵活控制

不能针对多个共享变量同时进行 CAS 操作,因为这多个变量之间是独立的,简单的把原子操作组合到一起,并不具 备原子性。因此如果我们想对多个对象同时进行 CAS 操作并想保证线程安全的话,是比较困难的