Java的CAS是如何实现的、ABA问题
CAS的关键实现
在 Java 中,实现 CAS操作的一个关键类是Unsafe,位于sun.misc包下:
A collection of methods for performing low-level, unsafe operations. Although the class and all methods are public, use of this class is limited because only trusted code can obtain instances of it. Note: It is the resposibility of the caller to make sure arguments are checked before methods of this class are called. While some rudimentary checks are performed on the input, the checks are best effort and when performance is an overriding priority, as when methods of this class are optimized by the runtime compiler, some or all checks (if any) may be elided. Hence, the caller must not rely on the checks and corresponding exceptions!
该类并不推荐开发者在应用程序中使用,而是用于 JVM 内部或一些需要极高性能和底层访问的库中。
sun.misc.
Unsafe
类提供了compareAndSwapObject
、compareAndSwapInt
、compareAndSwapLong
方法来实现的对Object
、int
、long
类型的 CAS 操作,如:
private static final jdk.internal.misc.Unsafe theInternalUnsafe = jdk.internal.misc.Unsafe.getUnsafe();@ForceInlinepublic final boolean compareAndSwapInt(Object o, long offset,int expected,int x) {return theInternalUnsafe.compareAndSetInt(o, offset, expected, x);}
其中theInternalUnsafe是jdk.internal.misc包下Unsafe的实例,它的compareAndSetInt方法则是本地方法,如下:
@HotSpotIntrinsicCandidatepublic final native boolean compareAndSetInt(Object o, long offset,int expected,int x);
Unsafe的具体使用
在juc包的atomic包提供了一些原子操作类,这些atomic类依赖于CAS乐观锁保证原子性,以AtomicInteger核心源码为例:
// 获取 Unsafe 实例
private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
private static final long valueOffset;static {try {// 获取“value”字段在AtomicInteger类中的内存偏移量valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));} catch (Exception ex) { throw new Error(ex); }
}
// 确保“value”字段的可见性
private volatile int value;// 如果当前值等于预期值,则原子地将值设置为newValue
// 使用 Unsafe#compareAndSwapInt 方法进行CAS操作
public final boolean compareAndSet(int expect, int update) {return U.compareAndSwapInt(this, valueOffset, expect, update);
}// 原子地将当前值加 delta 并返回旧值
public final int getAndAdd(int delta) {return U.getAndAddInt(this, valueOffset, delta);
}// 原子地将当前值加 1 并返回加之前的值(旧值)
// 使用 Unsafe#getAndAddInt 方法进行CAS操作。
public final int getAndIncrement() {return U.getAndAddInt(this, valueOffset, 1);
}// 原子地将当前值减 1 并返回减之前的值(旧值)
public final int getAndDecrement() {return U.getAndAddInt(this, valueOffset, -1);
}
sun.internal.misc.Unsafe类有如下方法:
// 原子地获取并增加整数值
public final int getAndAddInt(Object o, long offset, int delta) {int v;do {// 以 volatile 方式获取对象 o 在内存偏移量 offset 处的整数值v = getIntVolatile(o, offset);} while (!compareAndSwapInt(o, offset, v, v + delta));// 返回旧值return v;
}
CAS执行流程
- 线程从主存读取要修改的值存到本地线程缓存中
- 执行 CAS 操作,将本地线程缓存中的值与主内存中的值进行比较;
- 如果本地线程缓存中的值与主内存中的值相等,则将需要修改的值在本地线程缓存中修改;
- 如果修改成功,将修改后的值写入主内存,并返回修改结果;如果失败,则返回当前主内存中的值;
- 在多线程并发执行的情况下,如果多个线程同时执行 CAS 操作,只有一个线程的 CAS 操作会成功,其他线程的 CAS 操作都会失败,这也是 CAS 的原子性保证。
CAS问题
1.ABA问题
线程读取某变量的时候值为A,再次读取的时候值仍为A,不能说明该变量是否被其他线程改过,解决思路是在变量前面追加上版本号或者时间戳。JDK 1.5 以后的
AtomicStampedReference
类就是用来解决 ABA 问题的,其中的compareAndSet()
方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
2.循环时间长开销大
3.只能保证一个共享变量的原子操作