上一章J.U.C|乐观锁为何物我们简单的介绍了乐观锁的大概执行流程,本章将详细介绍下其主要的实现方式CAS。
什么是CAS
- CAS 全名 Compare and swap (比较并交换)
- CAS 是乐观锁技术,当多个线程使用CAS技术尝试更新主存中的同一数据时、只能有一个线程更新成功、其它线程更新失败、失败的线程并不会挂起、而是被告知更新失败,失败后可以继续尝试或者报错退出。
- CAS 无锁算法,基于硬件原语实现的,在不使用锁(没有线程被阻塞)的情况下实现多线程变量之间的同步问题。
- JDK中的实现,J.U.C包下的原子包就是通过CAS来实现的(整个包就是通过CAS来实现的,由此可见其重要性)。
CAS操作数
算法涉及三个操作数
- 需要读写的内存值V
- 进行比较的值A
- 要写入的新值B
CAS操作流程

CAS概念和流程到这基本有个一个清晰的认识了, 上面提到CAS是基于硬件原语来实现下面我们来通过源码简单介绍下(涉及到native方法的一些C/C++实现不做介绍)
CPU指令对CAS的支持
或许我们可能会有这样的疑问,假设存在多个线程执行CAS操作并且CAS的步骤很多,有没有可能在判断V和A相同后,正要赋值时,切换了线程,更改了值。造成了数据不一致呢?答案是否定的,因为CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。
Unsafe 类介绍
Unsafe类在sun.misc包下面,在Java中CAS的操作都是基于Unsafe类实现的,其内部都是方法都是Native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务, 下面我们来了解下CAS操作相关的主要方法。
// var1 操作对象, var2 对象内存的偏移量, var4 预期值, var5 表示要设置的值, 下面三方法底层都是通过CAS原子指令操作的。
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
Unsafe 类中CAS操作相关的方法基本上就是这三个。下面我们通过AtomicInteger类来看看CAS使用。
Atomic系列
通过前面的分析我们已基本理解了无锁CAS的原理并对Java中的指针类Unsafe类有了比较全面的认识,下面进一步分析CAS在Java中的应用,即并发包中的原子操作类(Atomic系列),从JDK 1.5开始提供了java.util.concurrent.atomic包,在该包中提供了许多基于CAS实现的原子操作类,用法方便,性能高效, 我们主要通过AtomicInteger源码分析下。
AtomicInteger源码主要方法
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// 硬件级别操作,指针类
private static final Unsafe unsafe = Unsafe.getUnsafe();
// value 变量在 AtomicInteger 中的偏移量
private static final long valueOffset;
static {
try {
//通过unsafe类的objectFieldOffset()方法,获取value变量在对象内存中的偏移
//通过该偏移量valueOffset,unsafe类的内部方法可以获取到变量value对其进行取值或赋值操作
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
//当前类中封装的int value值, vvolatile 修饰保证内存的可见性
private volatile int value;
//获取当前值
public final int get() {
return value;
}
// 设置新值回返回旧值,底层调用CAS操作
public final int getAndSet(int newValue) {
return unsafe.getAndSetInt(this, valueOffset, newValue);
}
// 如果当前值等于预期值expect, 则更新当前值为update
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
// 当前值+1 返回旧值
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
// 当前值 - 1 返回旧值
public final int getAndDecrement() {
return unsafe.getAndAddInt(this, valueOffset, -1);
}
//当前值 + data 返回旧值
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
// 当前值 + 1 返回新值
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
//当前值 - 1 返回新值
public final int decrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
}
通过上述源码我们可以看到其基本原子操作基本都是调用UnSafe中CAS操作来实现的,下面我们来根据自增方法来分析下。
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
AtomicInteger 中原子自增操作,我们可以看通过unsafe.getAndAddInt(this, valueOffset, 1)方法来实现的。
// var1 操作对象, var2 对象内存中的偏移量, var4 更新值
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
// 自旋
do {
// 通过偏移量在对象内存中获取值。
var5 = this.getIntVolatile(var1, var2);
// 自旋 通过CAS操作来更新内存中的值
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
// ? 注意返回的的是旧值
return var5;
}
写到这对CAS的介绍已经完了, 上面介绍了通过CAS操作给我们带来了很多性能的提升,下面我们来看看其缺点。

ABA问题来源

如上图线程2 内存地址V初次读取的值是0,并且在准备赋值的时候检查到它的值仍然为0,那我们就能说它的值没有被其他线程改变过了吗?
这期间已经被线程3将内存值V从1改为0 ,那么线程2就认为其内存值没有被改变过,这就是CAS操作的漏洞'ABA'问题(个人感觉ABA其实影响不大,只是丢失了其一个过程的记录而已,对整个业务的结果没有产生影响)。
~~~~~~~~上述就是对乐观锁主要实现方式CAS的介绍, 看完希望有收获~~~~~~~~
网友评论