Java基础-0x01:学习CAS

JUC

CAS你知道吗

CASCompare and swap),即比较并交换,也是实现我们平时所说的自旋锁或乐观锁的核心操作

它的实现很简单,就是用一个预期的值和内存值进行比较,如果两个值相等,就用预期的值替换内存值,并返回 true。否则,返回 false

比较并交换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 1. CAS是什么? ==> CompareAndSwap<br>
* 比较并交换
*
* @author allwayz
*/
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(5);
//真实值和期望值相同,修改成功
System.out.println(
atomicInteger.compareAndSet(5,2020)+
"\t current atomicInteger: "+atomicInteger.toString());
//第二次真实值已经被修改,则与期望不符,修改失败
System.out.println(
atomicInteger.compareAndSet(5,2020)+
"\t current atomicInteger: "+atomicInteger.toString());
}
}

CAS底层原理是什么 谈谈对Unsafe的理解

底层原理:

  • 自旋锁
  • UnSafe类

atomicInteger.getAndIncrement();

源码:

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
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final int getAndIncrement() {
/*
this: 当前对象
valueOffset:内存偏移量,内存地址
*/
return unsafe.getAndAddInt(this, valueOffset, 1);
}

/**
* Object: 当前对象
* long: 内存地址
* int: 固定是1
*
* {@Link sun.misc.UnSafe.getAndAddInt()}
*/
public 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;
}

UnSafe

CAS的核心类,由于Java方法无法直接访问底层系统。需要通过本地(native)方法来访问,UnSafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在于 sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法。

注意⚠️:Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中所有的方法都直接调用操作系统底层资源执行相应任务

CAS是什么

全称为Compare-And-Swap,是一条CPU并发原语。

它的功能是判断内存某个未知的值是否为预期值,如果是,则更改为新的值,这个过程是原子的。

CAS并发原语体现在Java语言中就是sun.misc.Unsafe类中的各个方法。强调Unsafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题

假设线程A和线程B两个线程同时执行getAndAddInt操作;

  1. AtomicInteger的里面的value原始值为3,即主内存中AtomicInteger的value为3,根据JMM模型,线程A和线程B各自持有一份value = 3的副本分别到各自的工作内存。
  2. 线程A通过getIntVolatile(var1, var2)拿到value值3,这是线程A被挂起。
  3. 线程B也通过getIntVolatile(var1, var2)拿到value值3,此时刚好线程B没有被挂起,并执行compareAndSwapInt方法,比较内存值也为3,成功修改内存值为4,线程B结束。
  4. 这时线程A恢复,执行从没怕热A你的SWAPInt方法,发现工作内存中的值 3 与主内存中的值 4 不一致,说明其他线程已经抢先一步修改过了,那么线程A本次修改失败,只能重新再读取一遍。
  5. 线程A重新获取主内存中Value,因为变量value被Volatile修饰,所以其他线程对他的修改线程A总能看到,线程A继续执行compareAndSwapInt进行比较替换,直到成功。
底层汇编
1
2
3
4
5
6
7
8
UNSAFE_ENTRY(jboolean,Unsafe_CompareAndSwapInt(JNIEnv *env,jobject unsafe,jobject obj,jlong offset,jint e,jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
jint*addr = (jinr*)index_oop_from_field_offset_long(p,offset);
return (jint)(Atomic::cmpxchg(x,addr,e)) == e;
UNSAFE_END
//先想办法拿到变量Value在内存中的地址
//通过Atomic::cmpxchg实现比较并替换,其中参数x是即将更新的值,参数e是内存的值

CAS缺点

  • 循环时间长 开销很大

在源码中,我们可以看到getAndAddInt方法,有个do while loop。

1
2
3
4
5
6
7
public 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;
}

如果CAS失败,会一直尝试。如果CAS长时间不成功,可能会给CPU带来很大的开销。

  • 只能保证一个共享变量的原子操作

当对一个共享变量进行操作时,我们可以使用CAS的方式来保证原子操作

但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用来保证原子性。

  • 引出来ABA问题