【JUC】 Java中的CAS
1.什么是CAS?
CAS:Conmpare And Swap ---- 比较和交换
在计算机科学中,比较和交换(Conmpare And Swap)是用于实现多线程同步的原子指令。 它将内存位置的内容与给定值进行比较,只有在相同的情况下,将该内存位置的内容修改为新的给定值。
—*
2.Java中的CAS
Java中,在JDK 5之前Java语言是靠synchronized关键字保证同步,导致存在以下几个问题:
1,在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
2,一个线程持有锁会导致其它所有需要此锁的线程挂起。
3,如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险。
volatile是不错的机制,之前我们聊过volatile关键字,传送门:volatile关键字相关整理
但是volatile不能保证原子性。因此对于同步最终还是要回到锁机制上来。
synchronized是一种悲观锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。
而另一个更加有效的锁就是乐观锁。
所谓乐观锁,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁用到的机制就是CAS,Compare and Swap。
所以,JAVA1.5开始引入了CAS,主要代码都放在JUC的atomic包下:
3.JAVA中的CAS操作
AtomicInteger为例,先上一个Demo
package com.test.cas;
import java.util.concurrent.atomic.AtomicInteger;
/**
* CAS是什么?
* 1.1 比较交换 ---> compareAndSet
*
* @author wangjie
* @version V1.0
* @date 2019/12/15
*/
public class CASDemo {
public static void main(String[] args) {
//新建一个AtomicInteger类,初始值为5
AtomicInteger atomicInteger = new AtomicInteger(5);
//此时AtomicInteger值为5,进行比较交换,期望值5,新的值2019
System.out.println("此时AtomicInteger值为"+atomicInteger.get()+"交换比较结果:"+atomicInteger.compareAndSet(5, 2019)+"\t current :"+atomicInteger.get());
//此时AtomicInteger值为2019,进行比较交换,期望值5,新的值2014
System.out.println("此时AtomicInteger值为"+atomicInteger.get()+"交换比较结果:"+atomicInteger.compareAndSet(5, 2014)+"\t current :"+atomicInteger.get());
}
}
运行结果:
该方法的说明:
如果当前状态值等于预期值,则以原子方式将同步状态设置为给定的更新值,并返回true。
如果当前状态值不等于预期值,则返回false。
4. Java中CAS的底层原理
我们仔细分析JUC包里的实现,会发现一个通用的实现模式:
1,首先,声明共享变量为volatile;
2,然后,使用CAS的原子条件更新来实现线程之间的同步;
3,同时,配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信。
同样以AtomicInteger为例,先从他的getAndIncrement()方法入手,这是个对自身值自增的方法,类似i++:
引出来一个新问题:UnSafe类是什么?
变量value和volatile修饰,保证了多线程之间的可见性.
UnSafe
是CAS的核心类 由于Java 方法无法直接访问底层 ,需要通过本地(native)方法来访问,UnSafe相当于一个后面,基于该类可以直接操作特额定的内存数据.UnSafe类在于sun.misc包中,其内部方法操作可以向C的指针一样直接操作内存。
注意UnSafe类中所有的方法都是native修饰的,也就是说UnSafe类中的方法都是直接调用操作底层资源执行响应的任务
而变量ValueOffset,便是该变量在内存中的偏移地址,因为UnSafe就是根据内存偏移地址获取数据的。
比较和交换:
5.总结
总结一下:
CAS的全称为Compare-And-Swap ,它是一条CPU并发原语.
它的功能是判断内存某个位置的值是否为预期值,如果是则更新为新的值,这个过程是原子的.
CAS并发原语在Java语言中的体现就是sun.miscUnSaffe类中的各个方法。
调用UnSafe类中的CAS方法,JVM会帮我实现CAS汇编指令。
这是一种完全依赖于硬件 功能,通过它实现了原子操作,再次强调,由于CAS是一种系统原语,原语属于操作系统用于范畴,是由若干条指令组成,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许中断,也即是说CAS是一条原子指令,不会造成所谓的数据不一致的问题.
6.CAS缺点
6.1 循环时间长开销大
还是这个AtomicInteger的getAndIncrement()方法底层为例:
方法执行时有个都 do while,如果CAS失败,会一直尝试,会给CPU带来很大的开销。
6.2 只能保证一个共享变量的原子操作
当对一个共享变量执行操作时,我们可以使用循环CAS的方式保证原子操作。
但是,对多个共享变量操作时,CAS就失去了原子性。
6.3 ABA问题
CAS算法实现的一个很重要的前提是需要取出内存中某个时刻的数据并在当下时刻比较并替换,那么在这个时间差内数据变化会产生ABA问题。
比如,线程T1从内存位置V取出A,同时,线程T2也从内存位置V取出A,并且线程T2进行了一些操作,快速的把A换成了B,又快速的把B换成了A,之后线程T1进行CAS操作,发现内存位置V的值还是A,操作成功。
尽管线程T1的CAS操作成功,但并不代表这个过程就是没问题的。
具体解决方法:【JUC】CAS中的ABA问题和解决方案
【完】
注:文章内所有测试用例源码:https://gitee.com/wjie2018/test-case.git
上一篇: maven详解——转
下一篇: MySQL的MVCC原理