synchronized 属于悲观锁,CAS(Compare and Swap,比较并交换)则属于乐观锁,是一种高效实现线程安全性的方法,支持原子更新操作,适用于计数器等场景。CAS 操作失败时由开发者决定是继续尝试,还是执行别的操作,因此支持失败的线程不会被阻塞挂起。
CAS原理
CAS 机制使用了 3 个基本操作数:内存地址 V,预期原值 A 和新值 B。
更新一个变量的时候,只有当变量的预期原值 A 和内存地址 V 当中的实际值相同时,才会将内存地址 V 对应的值修改为 B,否则 CPU 不做任何操作。
这样说或许有些抽象,我们来看一个例子:
- 在内存地址 V 当中,存储着值为 10 的变量;
- 此时线程 1 想要把变量的值增加 1,对线程 1 来说,旧的预期值 A=10,要修改的新值 B=11;
- 在线程 1 要提交更新之前,另一个线程 2 抢先一步,把内存地址 V 中的变量值率先更新成了 11;
- 线程 1 开始提交更新,首先进行 A 和地址 V 的实际值比较(Compare),发现 A 不等于 V 的实际值,提交失败;
- 线程 1 重新获取内存地址 V 的当前值,并重新计算想要修改的新值,此时对线程 1 来说,A=11,B=12,这个重新尝试的过程被称为自旋;
- 这一次没有其他线程改变地址 V 的值,线程 1 进行 Compare,发现 A 和地址 V 的实际值是相等的;
- 线程 1 进行 SWAP,把地址 V 的值替换为 B,也就是 12。
那么在 Java 中都有哪些地方用到了 CAS 机制呢?
有许多地方用到,包括上面说的 Atomic 操作类以及 Lock 系列类的底层实现。甚至在 JDK1.6 以上版本,synchronized 转变为重量级锁之前,也会采用 CAS 机制。
AtomicInteger源码分析
AtomicInteger 是通过 sun.misc.Unsafe 这个硬件级别的原子操作类来实现 CAS 操作的。下面看下 AtomicInteger.java 的部分实现:
package java.util.concurrent.atomic;
import sun.misc.Unsafe;
public class AtomicInteger extends Number implements java.io.Serializable {
// 取得Unsafe对象
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); }
}
private volatile int value; // 初始Integer大小
// 省略了部分代码...
public AtomicInteger(int initialValue) {
value = initialValue;
}
// 无参数构造函数, 默认初始大小为0
public AtomicInteger() {
}
// 返回旧值, 并设置新值为newValue
public final int getAndSet(int newValue) {
return unsafe.getAndSetInt(this, valueOffset, newValue);
}
// 获取当前值, 并设置新值+1, 同value++
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
// 省略了部分代码...
}
看下 getAndSet() 中 unsafe.getAndSetInt() 方法的实现:
public final class Unsafe {
public final int getAndSetInt(Object o, long offset, int delta) {
int v;
do {
// 获得当前主内存中的值
v = this.getIntVolatile(o, offset);
// CAS 操作更新值, 如果失败就一直重试
} while(!this.compareAndSwapInt(o, offset, v, delta));
return v;
}
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
// 省略了部分代码...
}
CAS总结
CAS 多数情况下对开发者来说是透明的:
- JUC 的 atomic 包(java.util.concurrent.atomic)提供了常用的原子性数据类型以及引用、数组等相关原子类型和更新操作工具,是很多线程安全程序的首选;
- Unsafe 类虽然提供了 CAS 操作,但因能够操作任意内存地址读写而有隐患;
- Java 9 及以上,可以使用 Variable Handle API 来替代 Unsafe 进行各种粒度的原子或者有序性的操作。
CAS 缺点:
- 若更新一个变量,一直更新不成功而进行循环,则 CPU 开销很大;
- 能保证代码块的原子性,只能保证一个共享变量的原子操作;
- ABA 问题,这是 CAS 机制最大的问题所在。
什么是 ABA 问题?怎么解决?
如果地址 V 初次读取的值是 a,并且在准备赋值的时候检查到它的值仍然为 a,我们就可以说它的值没有被其他线程改变过了吗?是不能的,有可能中间被改成了 b,又改回了 a。而 CAS 操作就会认为从来没有被改过。这个问题就是 CAS 的 ABA 问题。
JUC 为了解决 ABA 问题,提供了一个带有标记的原子引用类 AtomicStampedReference,它可以通过变量值的版本来保证 CAS 的正确性。
在使用 CAS 前要考虑好 ABA 问题是否影响程序并发的正确性,如果需要解决 ABA 问题,改用传统的 synchronized 可能会比原子类更高效。