一、什么是CAS
CAS(Compare And Swap/Set),比较并交换。是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS 算法涉及到三个操作数:
- 需要读写的内存值V
- 进行比较的值A
- 拟写入的新值B
当且仅当 V 的值等于 A 时, CAS 通过原子方式用新值 B 来更新 V 的值, 否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。工作原理流程图如下:
【注】CAS是系统原语,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行的过程中不允许被打断,因此不会造成所谓的数据不一致问题,是线程安全的。
二、CAS在JUC下的应用
jdk1.8下 java.util.concurrent.atomic包中有17个类,如下
1、基本类型原子类:使用原子的方式更新基本类型
- AtomicInteger:int类型原子类
- AtomicLong:long类型原子类
- AtomicBoolean:boolean类型原子类
以AtomicInteger为例:
public static void main(String[] args) {
AtomicInteger atomicInteger1 = new AtomicInteger(2022);
AtomicInteger atomicInteger2 = new AtomicInteger(2022);
//比较并交换
atomicInteger1.compareAndSet(2021, 2023);
atomicInteger2.compareAndSet(2022, 2023);
System.out.println("1修改后:" + atomicInteger1);
System.out.println("2修改后:" + atomicInteger2);
}
打印结果:
1修改后:2022
2修改后:2023
2、数组类型原子类:使用原子的方式更新数组里的某个元素,可以确保修改数组中数据的线程安全性。
- AtomicIntegerArray:整形数组原子操作类
- AtomicLongArray:长整形数组原子操作类
- AtomicReferenceArray:引用类型数组原子操作类
以AtomicIntegerArray为例:
public static void main(String[] args) {
int[] arr = new int[]{1,2,3,4,5};
AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(arr);
atomicIntegerArray.compareAndSet(2,3,100);
System.out.println("AtomicIntegerArray修改后:" + atomicIntegerArray);
}
打印结果:
AtomicIntegerArray修改后:[1, 2, 100, 4, 5]
3、引用类型原子类:基本类型原子类只能更新一个变量,如果需要原子更新多个变量,需要使用引用类型原子类
- AtomicReference:引用类型原子类
- AtomicStampedReference:原子更新引用类型里的字段原子类
- AtomicMarkableReference:原子更新带有标记位的引用类型
以 AtomicReference 为例:10个人每人向张三账户汇款100元
public class AtomicReferenceText {
private static AtomicReference<BankCard> atomicReference = new AtomicReference<>(new BankCard("张三", 100));
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
BankCard bankCard;
BankCard newBankCard;
do {
bankCard = atomicReference.get();
newBankCard = new BankCard(bankCard.getAccount(), bankCard.getAmount() + 100);
} while (!atomicReference.compareAndSet(bankCard, newBankCard));
System.out.println("线程:" + Thread.currentThread().getName() + " 汇款到账户成功,当前金额:" + newBankCard.getAmount());
}).start();
}
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class BankCard {
//账户
private String account;
//金额
private int amount;
}
结果打印:
线程:Thread-0 汇款到账户成功,当前金额:300
线程:Thread-8 汇款到账户成功,当前金额:600
线程:Thread-2 汇款到账户成功,当前金额:500
线程:Thread-6 汇款到账户成功,当前金额:800
线程:Thread-5 汇款到账户成功,当前金额:400
线程:Thread-1 汇款到账户成功,当前金额:200
线程:Thread-7 汇款到账户成功,当前金额:900
线程:Thread-3 汇款到账户成功,当前金额:700
线程:Thread-4 汇款到账户成功,当前金额:1100
线程:Thread-9 汇款到账户成功,当前金额:1000
4、对象的属性修改原子类:如果需要原子更新某个类里的某个字段时,需要用到对象的属性修改原子类
- AtomicIntegerFieldUpdater:原子更新整形字段的值
- AtomicLongFieldUpdater:原子更新长整形字段的值
- AtomicReferenceFieldUpdater:原子更新应用类型字段的值
5、分散热点的高性能原子更新:jdk1.8中引入了5个类
- Striped64:高并发原子累加器
- LongAdder:处理 long 类型分段锁并发,性能比 AtomicLong 好
- LongAccumulator:用于自定义运算规则场景下的多线程并发场合
- DoubleAdder:处理 double 类型分段锁并发
- DoubleAccumulator:用法与LongAccumulator基本相同,只是处理类型不同
三、CAS的原理
1、Unsafe类
Unsafe 是 CAS 的核心类,由于 Java 方法无法直接访问底层系统,而是通过本地 (native) 方法来访问,Unsafe 相当于一个后门。基于该类可以操作特定内存的数据。Unsafe 类存在于sun.misc (jre文件夹下rt.jar) 包中,其内部方法操作可以像C的指针一样直接操作内存,因为 java 中的 CAS 操作的执行依赖于 Unsafe 类的方法。
【注】Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的所有方法都可以直接调用操作系统。
2、变量 valueOffset,表示该变量值在内存中的偏移地址(内存地址),因为 Unsafe 就是根据内存偏地址获取数据的
3、变量 value 用 volatile 修饰,保证了多线程之间的内存可见性。
源码如下:
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// 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); }
}
private volatile int value;
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
}