14.1 基本概念
CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并交换。
CAS机制当中使用了3个基本操作数:内存地址(initialValue),旧的预期值(expect),要修改的新值(update)。
更新一个变量的时候,只有当变量的预期值(expect)和内存地址(initialValue)当中的实际值相同时,才会将内存地址(initialValue)对应的值修改为(update)。
源码分析
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
// 参数: 期望值 更新值
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
代码示例
真实值和期望值相同,就修改成功,真实值和期望值不同,就修改失败!!!
package cn.guardwhy.cas01;
import java.util.concurrent.atomic.AtomicInteger;
/*
CAS : 比较并交换 compareAndSet
*/
public class CASDemo01 {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2011);
/*
源码示例 参数: 期望值 更新值
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
*/
// 期望值是 2011 ,后来改为2021 所以结果为2021
System.out.println(atomicInteger.compareAndSet(2011, 2021)); // true
System.out.println(atomicInteger.get()); // 2021
// 期望值是2011,后改为1024 所以最终结果为false 2021
System.out.println(atomicInteger.compareAndSet(2011, 1024)); // false
System.out.println(atomicInteger.get()); // 2021
}
}
14.2 CAS底层原理
atomicInteger.getAndIncrement( ) ; 这里的自增 + 1 是如何实现的。
atomicInteger.getAndIncrement(); // 分析源码,如何实现的i++安全的问题
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final int getAndIncrement() {
// this表示当前对象, valueOffset内存偏移量,内存地址,固定写死
return unsafe.getAndAddInt(this, valueOffset, 1);
}
14.2.1 Unsafe类
UnSafe是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,UnSafe相当于一个后门,基于该类可以直接操作特定内存的数据,Unsafe类存在于 sun.misc包中,其内部方法操作可以像C++的指针一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法。
注意:Unsafe类中的所有方法都是Native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务。
14.2.2 变量valueOffset
表示该变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的。
14.2.3 源码分析
Compare-And-Swap是一条CPU并发原语,是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。CAS并发原语体现在JAVA语言中就是 sun.misc.Unsafe 类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用于范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。
底层是自旋锁
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
// 自旋锁
do {
// 获取传入对象的地址
var5 = this.getIntVolatile(var1, var2);
// 比较并且交换,如果var1, var2还是原来的var5,就执行内存偏移 + 1, var5 + var4操作
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
14.3 CAS总结
CAS(CompareAndSwap)
比较当前工作内存中的值和主内存中的值,如果相同则执行规定操作,否则继续比较直到主内存和工作内存中的值一致为止(自旋锁)。
CAS 的缺点
1、循环会很耗时
- 源码中存在 一个 do…while 操作,如果CAS失败就会一直进行尝试。
2、只能保证一个共享变量的原子操作。
- 当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作。
- 对多个共享变量操作时,循环CAS就无法保证操作的原子性,这时候只能用锁来保证原子性。
14.4 原子引用
14.4.1 ABA问题(狸猫换太子)
CAS算法实现前提:需要取出内存中某时刻的数据并在当下时刻比较并交换,那么在这个时间差内会导致数据的变化。
案例分析
假设一个线程a从内存取出A (A=1) , 线程b也从内存中取出A (A=1) ,但是线程b速度比较快,把A进行了CAS(1, 3)操作,然后线程b又将内存A进行CAS(3,1)操作。此刻线程a 进行CAS操作发现内存中仍然是A,然后线程a操作成功。尽管线程a的CAS操作成功,但是这个过程是有问题的。
代码示例
package cn.guardwhy.cas01;
import java.util.concurrent.atomic.AtomicInteger;
// CAS : 比较并交换 compareAndSet
public class CASDemo02 {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2011);
/*
期望、更新
public final boolean compareAndSet(int expect, int update)
如果期望值达到了,那么就更新,否则就不更新,CAS是CPU的并发原语
*/
System.out.println("======捣乱的线程========");
System.out.println(atomicInteger.compareAndSet(2011, 2021)); // true
System.out.println(atomicInteger.get()); // 2021
System.out.println(atomicInteger.compareAndSet(2021, 2011)); // true
System.out.println(atomicInteger.get()); // 2011
System.out.println("======期待的线程========");
System.out.println(atomicInteger.compareAndSet(2011, 123)); // true
System.out.println(atomicInteger.get()); // 123
}
}
14.2 AtomicStampedReference
带版本号的原子操作,类似于乐观锁。
源码分析
# 创建AtomicStampedReference对象
AtomicStampedReference<Integer> AtomicStampedReference = new AtomicStampedReference<>(1,1);
# 2.查看AtomicStampedReference源码
/**
* Creates a new {@code AtomicStampedReference} with the given
* initial values.
*
* @param initialRef the initial reference
* @param initialStamp the initial stamp
*/
public AtomicStampedReference(V initialRef, int initialStamp) {
// initialValue:初始值 initialStamp:初始的时间戳
pair = Pair.of(initialRef, initialStamp);
}
构造方法
源码分析
/**
* Atomically sets the value of both the reference and stamp
* to the given update values if the
* current reference is {@code ==} to the expected reference
* and the current stamp is equal to the expected stamp.
*
* @param expectedReference the expected value of the reference
* @param newReference the new value for the reference
* @param expectedStamp the expected value of the stamp
* @param newStamp the new value for the stamp
* @return {@code true} if successful
*/
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp, // 拿到最新的版本号
int newStamp) { // 版本号+1操作(更新)
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
14.3 代码示例
package cn.guardwhy.cas01;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
/*
* 解决ABA问题
*/
public class ABADemo03 {
public static void main(String[] args) {
// 1. 正常业务操作时候,泛型类型一般都是user对象
AtomicStampedReference<Integer> atomicStampedReference
= new AtomicStampedReference<>(1,1); // 两个值: 初始值,初始版本号(初始的时间戳)
// 2.1CAS:比较并交换
new Thread(()->{
// 2.2 获得最新的版本号
int stamp = atomicStampedReference.getStamp();
// 拿到最新版本
System.out.println("a1==>" + stamp);
try {
// 2.3 休眠1s
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(1, 2,
// 拿到最新的版本号,版本号+1操作(更新)
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
// 拿到最新版本号
System.out.println("a2==>" + atomicStampedReference.getStamp());
System.out.println(atomicStampedReference.compareAndSet(2, 1,
// 拿到最新的版本号,版本号+1操作(更新)
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
// 拿到最新版本号
System.out.println("a3==>" + atomicStampedReference.getStamp());
},"a").start();
new Thread(()->{
// 获得最新版本号
int stamp = atomicStampedReference.getStamp();
System.out.println("b1==>" + stamp);
try {
// 休眠2s
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(1, 66,
stamp, stamp + 1));
System.out.println("b2==>" + atomicStampedReference.getStamp());
}, "b").start();
}
}
执行结果