文章目录
1. 什么是CAS
2. AtomicInteger
3. CAS缺陷
4. ABA问题及解决方案
1. 什么是CAS
CAS是Compare and swap的缩写,即比较交换。由JDK提供的非阻塞原子性操作,它通过硬件保证了比较更新操作的原子性。
CAS算法过程: 它包含3个参数 CAS(V,A,B)
- V: 表示要更新的变量(内存值)
- A: 表示预期值(旧的)
- B: 表示要更新的值(新值)
当且仅当内存值V的值等于旧的预期值A时,才会将内存值V的值修改为B,否则什么都不干
J.U.C下的Atomic类,都是通过CAS来实现的。以AtomicInteger为例,来阐述CAS的实现,如下:
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
- Unsafe是CAS的核心类,Java无法直接访问底层操作系统,而是通过本地native方法来访问。不过尽管如此,JVM还是开了一个后门:Unsafe,它提供了硬件级别的原子操作
valueOffset
为变量值在内存中的偏移地址,Unsafe就是通过偏移地址来得到数据的原值的value
当前值,使用volatile
修饰,保证多线程环境下看见的是同一个
2.AtomicInteger
以AtomicInteger的 # addAndGet()
方法来说明,源代码:
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
- 内部调用
unsafe
的#getAndAddInt(Object var1, long var2, int var4)
方法,在#getAndAddInt(Object var1, long var2, int var4)
方法中,主要看compareAndSwapInt(var1, var2, var5, var5 + var4)
方法,代码如下:
该方法为本地方法,有四个参数,操作含义: 如果对象obj中内存偏移量var2的变量值为var4,则使用新的值var5替换旧的值var4public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
- var1: 对象内存位置
- var2: 对象中的变量的偏移量
- var4: 变量预期值(旧的)
- var5: 新的值
最终底层实现(仅了解)
最终实现: cmpxchg = cas修改变量值
lock cmpxchg 指令
硬件: lock指令在执行后面指令的时候锁定一个北桥信号(不采用锁总线的方式)
3. CAS缺陷
CAS虽然高效解决了原子操作,但是还是存在一些缺陷,主要体现在三个方面:
-
循环时间太长
CAS里面是一个循环判断的过程, 如果自旋 CAS 长时间地不成功,CPU资源会被一直占用。
在 J.U.C 中,有些地方就限制了 CAS自旋的次数,例如: BlockingQueue 的 SynchronousQueue 。 -
只能保证一个共享变量原子操作
CAS 的实现只能针对一个共享变量,如果是多个共享变量就只能使用锁了。
如果有办法把多个变量整成一个变量,利用 CAS也不错。例如读写锁中 state 的高低位。 -
ABA问题
CAS 需要检查操作值有没有发生改变,如果没有发生改变则更新。
一个线程 a 将数值改成了 b,接着又改成了 a,此时 CAS 认为是没有变化,其实是已经变化过了,解决方案可以使用版本号标识,每操作一次version 加 1。在 java5 中,已经提供了 AtomicStampedReference 来解决问题。
4. ABA问题及解决方案
4.2 原因
CAS 需要检查操作值有没有发生改变,如果没有发生改变则更新。
但是存在这样一种情况:如果一个值原来是 A,变成了 B,然后又变成了 A,那么在 CAS 检查的时候会发现没有改变,但是实质上它已经发生了改变,这就是所谓的ABA问题。
4.1 解决方案
对于 ABA 问题其解决方案是加上版本号,即在每个变量都加上一个版本号,每次改变时加 1 ,即 A —> B —> A ,变成1A —> 2B —> 3A 。
Java 提供了 AtomicStampedReference 来解决。AtomicStampedReference 通过对对象标记版本戳 stamp ,从而避免 ABA 问题。