CAS(1.0)
CAS
概述?
CAS的全称为Compare-And-Swap ,它是一条
CPU并发原语
,比较 工作内存值(预期值) 和 主物理内存 的共享值是否相同,相同则执行规定操作,否则继续比较直到主内存和工作内存的值一致为止。这个过程是原子的(AtomicInteger类主要利用 CAS(compare and swap)+volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升)
CAS并发原语是现在 Java 语言中就是 sun.misc包下的 UnSafe 类中的各个方法。调用 UnSafe 类中的 CAS 方法, JVM 会帮我实现 CAS 汇编指令。这是一种
完全依赖于硬件功能
,通过它实现了原子操作。由于CAS是一种系统原语,原语属于操作系统用于范畴,是由若干条指令组成,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许中断,也即是说CAS 是一条原子指令,不会造成所谓的数据不一致的问题
atomicInteger.getAndIncrement()
1
2
3public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}调用了 unsafe.getAndAddInt();
1
2
3
4
5
6
7public 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;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/**
* @Date 2022/8/28 15:22
* @Author c-z-k
*/
public class AtomicIntDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger();
int i = atomicInteger.incrementAndGet();
System.out.println("i = " + i);
int andIncrement = atomicInteger.getAndIncrement();
System.out.println("andIncrement = " + andIncrement);
System.out.println("atomicInteger.compareAndSet(2,3) = " + atomicInteger.compareAndSet(2, 3));
System.out.println("atomicInteger.compareAndSet(2,3) = " + atomicInteger.compareAndSet(2, 3));
}
}
Unsafe
UnSafe类中所有的方法都是native修饰的,也就是说UnSafe类中的方法都是直接调用操作底层资源执行响应的任务
- 是 CAS 的核心类,由于 Java 方法无法直接访问底层 ,需要通过本地(native)方法来访问, UnSafe 相当于一个后门,基于该类可以直接操作特定的内存数据。UnSafe类在于 sun.misc 包中,其内部方法操作可以向 C 的指针一样直接操作内存,因为 Java 中 CAS 操作依赖于UnSafe 类的方法
- AtomicInteger 中使用 unsafe。
- 变量 ValueOffset ,便是该变量在内存中的偏移地址,因为 UnSafe 就是根据内存偏移地址获取数据的,变量 value 和 volatile 修饰,保证了多线程之间的可见性
CAS的缺点
循环时间长开销很大
- 我们可以看到getAndInt方法执行时,有个do while
- 如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销
只能保证一个共享变量的原子性
- 当对一个共享变量执行操作时,我们可以使用循环 CAS 的方式来保证原子操作
- 对多个共享变量操作时,循环 CAS 就无法保证操作的原子性,这个时候就可以用锁来保证原子性
ABA问题的产生
比如一个线程 one 从内存位置 V 中取出A,这时候另一个线程 two 也从内存中取出 A,并且线程 two 进行了一些操作将值变成了B,然后线程 two又将 V 位置的数据变成 A,这时候线程 one 进行 CAS 操作发现内存中仍然是A,然后线程 one 操作成功。尽管线程 one 的CAS操作成功,但是不代表这个过程就是没问题的
代码演示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36package tech.chen.juccode.a13;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* @Date 2022/8/28 16:33
* @Author c-z-k
*/
public class ABADemo {
public static void main(String[] args) {
AtomicBoolean atomicBoolean = new AtomicBoolean(true);
new Thread(()->{
atomicBoolean.compareAndSet(true,false);
System.out.println(Thread.currentThread().getName() +"\t"+atomicBoolean.get());
},"t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
atomicBoolean.compareAndSet(false,true);
System.out.println(Thread.currentThread().getName() +"\t"+atomicBoolean.get());
},"t2").start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicBoolean.compareAndSet(true,false);
System.out.println("atomicBoolean.get() = " + atomicBoolean.get());
}
}
ABA 问题的解决方案
- ABA问题解决方案是使用 AtomicStampedReference 每修改一次都会有一个版本号
- AtomicStampedReference 用来解决 AtomicInteger 中的 ABA 问题,该 demo 企图将 integer 的值从0一直增长到1000,但当integer 的值增长到128后,将停止增长。出现该现象有两点原因:
- 使用int类型而非 Integer 保存当前值
- Interger对-128~127的缓存[这个范围才有效,不在这个范围comareAndSet会一直返回false
1 | package tech.chen.juccode.a13; |
运行结果:
false 100