目录
1. 什么是CAS?
2. Java中关于 CAS 的 API 在哪里?
3. CAS API方法和参数解析
4. CAS的底层实现
5. CAS是如何保证多核线程安全的?
6. CAS的缺点?
7. 如何避免ABA问题?
1. 什么是CAS?
CAS的全程是 "CompareAndSwap",翻译过来就是 "比较和替换"。
CAS的操作包含三个操作数——内存地址,期望值,新值。如果内存位置的值与期望值匹配,那么处理器就会自动将该位置的值更新为新值;否则,处理器不做任何操作。无论哪种情况,它都会在CAS指令之前返回该位置的值。
CAS可以理解为 "我认为位置V的值应该是A,如果有该值,则将B放在这个位置;否则,不更改该位置的值,只告诉我这个位置现在的值即可"。
2. Java中关于 CAS 的 API 在哪里?
Java中 CAS 的 API 方法定义在 sun.misc.Unsafe 类中,如下图所示中的三个方法,可以看到该类的名字叫 Unsafe(不安全的),三个方法都有 native 修饰,这些方法不是由 Java 语言编写的,而是由C语言编写的;总之记住一点就行了,这些 native 本地CAS方法是用C语言编写的,通常也不是让我们 Java 程序员去调用使用的,更多见到它们是在源码中碰到它们。
3. CAS API方法和参数解析
CAS的三个方法作用相似,它们都有四个参数,下面我来说一下它们的作用。
compareAndSwapObject:比较并交换 Object 类型数据;
compareAndSwapInt:比较并交换 Int 类型数据;
compareAndSwapLong:比较并交换 Long 类型数据;
var1:方法要操作的对象;
var2:方法要操作的对象中属性的偏移量(举个例子,我们有一个 User 用户对象,在堆中存放,但我们只获取该 User 对象的其中一个属性值,就需要通过偏移量来获取,例如 user.name 获取用户名称属性);
var3:表示想要修改的数据属性当前值(意思就是改之前是什么值);
var4:表示想要修改的数据的修改值(意思就是想改成什么值);
4. CAS的底层实现
这里我来简单解释一下CAS的底层实现,CAS是通过调用JNI代码来实现的。
JNI 全称 java.native.interface ,翻译过来就是 Java本地接口方法,允许Java调用其它语言,而Java中的这三个CAS方法底层是用C语言编写的,通过C语言调用底层CPU,最终映射到CPU指令集上得以实现。
5. CAS是如何保证多核线程安全的?
现在的计算机大多都是多核心处理器,那么在此种情况下,系统执行CAS操作之前,会先去判断当前系统是否为多核心系统。如果是,就会给 "总线" 加锁,尽管有多个线程,但只有一个线程能给总线加锁成功,加完锁之后就会去执行CAS操作,也就是说CAS操作的原子性是平台级别的。而且只能有一个线程在同一时刻能够去执行CAS运算,无法多线程并行CAS操作,这样就解决了多核线程的安全问题。
6. CAS的缺点?
上面说到了CAS的操作原理,比较并替换,CAS操作时需要先判断先前的值是否发生变化,未变化则CAS,变化了则不执行CAS,如果说A线程在执行CAS操作之前,B线程将需要判断的值 a 改为了 b ,然后又从 b 改成了 a。那么当A线程再去执行CAS操作的时候,是可以执行成功的,因为它不管中间过程,只要我线程A在CAS时值未发生变化,就可以替换,但不能避免发生过变化不影响操作这种情况的发生。
举个简单的例子,事务A要去对数据库中的字段 num (假设当前值为1)做加一操作,事务B在事务A之前先去执行了加一操作,值变成了2,但是执行过程中发生了异常,事务B回滚,又从2回滚到了1,此时事务A再去对 num 进行加一操作时,是可以成功的,因为值没有发生改变。这种情况我们通常称为ABA问题。
7. 如何避免ABA问题?
为了解决ABA问题,在Java中,就引入了版本号这一说法,很简单,我们执行CAS操作时,不是会比较当前值和期望值是否相同吗?那我们就再加入一个版本号,对当前可能出现ABA问题的数据加上一个版本号,每当有一个线程对当前数据发生过修改,版本号都会做一次变换,即便又改回了原来的值,版本号也是不一致的。这样我们在执行CAS操作时,不仅要满足为当前值与期望值相等,还要满足当前版本号与期望版本号相同,只要有一个不同,CAS操作就不会成功,这样就可以避免ABA问题的出现。