我们知道我们使用Volatile可以保证可见性,但不保证原子性,那么,如果我们不使用Lock锁和synchronized,我们该如何保证添加了volatile关键字的共享变量的原子性呢?
解决方案:我们可以使用Java中java.util.concurrent.atomic包下的原子类,来解决我们的原子性问题,而他们就是使用了CAS来实现的。
什么是CAS?CAS的全称是:Compare And Swap(比较),CAS是CPU的并发原语,是CPU广泛支持的一种对内存中共享数据进行操作的一种特殊的指令,CAS可以将比较和交换转换成原子操作,这个原子操作由CPU保证。
CAS原理——Unsafe类我们根据源码可知,我们的AtomicInteger类中有一个Unsafe类。我们知道Java是无法直接操作内存的,但是我们Java可以调用C++,而C++是可以操作内存的。比如我们的native 方法,而这个Unsafe类就是Java的后门,Unsafe类让Java拥有了像C语言的指针一样操作内存空间的能力,我们可以通过这个类来进行内存的操作。
我们的原子类AutomicInteger.incrementAndGet(),底层是调用了Unsafe类的getAndAddInt()方法
而Unsafe类中的getAndAddInt()方法底层使用的就是CAS,并且这个方法使用的是自旋锁。
CAS操作依赖3个值
内存中的值V
旧的预估值X
要修改的值B
首先自旋锁中的do代码块中,先会通过unsafe.getIntVolatile(Obj,ValueOffset),参数中传进去automicInteger对象、偏移量和1来获取内存中automicInteger中的value,然后内存中的value值赋值给var5,然后再根据自旋锁判断当前内存中的value值与var5的值进行比较,如果值相同,则将后面的var5+var4(1)赋值给value。如果值不同,则重新进入do代码块中获取内存中的value。
我们来举例一个多线程例子:比如有两条线程t1、t2,
这个流程就是CAS的原理。
CAS优点CAS虽然能保证我们的原子性操作,但是会出出现以下的问题:
1、循环时间的开销较大:对于资源竞争较为激烈的场景,如有多个线程,这个时候CAS自旋的概率就会比较大,从而浪费CPU资源,效率可能会低于synchronized。
2、只能保证一个共享变量的原子操作:当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证的原子性,这个时候就可以用锁。
3、可能会出现ABA问题。
什么是ABA问题?
ABA问题一句话概括就是(狸猫换太子),假设两条线程去操作同一个资源,线程A和线程B,线程A想通过CAS把资源1改成2,而线程B拿到资源后把资源从1改为3,又把3改为1,而此时线程A还以为1是以前的资源,其实线程A拿到的值已经被线程B动过手脚了,这就是ABA问题。
1、我们可以使用原子引用AtomicRefence来解决ABA问题。
什么是原子引用AtomicReference?说白了原子引用就是带版本号的原子操作。
AtomicStampedReference atomicStampedReference = new AtomicStampedReference(初始值,时间戳);
这个时间戳可以看成是版本号,如果我们把它设置为1,我们每次修改,我们都需要把这个值往上题,每次有人动了这个值,我们就知道这个值已经被人动过了,这根乐观锁是一样的原理。
在这里,如果我们使用的是 int 的包装类型 Integer ,那么我们就需要注意,我们的初始值不能设置大于128的,如果大于128的会出现CAS产生失败的现象。这是有一个大坑。
在阿里巴巴的开发手册中发现,Integer 采用了对象缓存机制,在-128至127之间赋值,Integer 对象会在IntegerCache.cache中获取,会复用已有的对象,但是这个区间以外的所有数据,都会在堆上产生,并不会复用已有的对象,这就导致了我们的引用不是同一个值,所有会修改失败。
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧
售后响应及时
7×24小时客服热线数据备份
更安全、更高效、更稳定价格公道精准
项目经理精准报价不弄虚作假合作无风险
重合同讲信誉,无效全额退款