狂神说Java:www.bilibili.com/video/BV1B7…
CAS机制及AtomicInteger源码分析:juejin.im/post/5e2182…
一:CAS简介
CAS:Compare And Swap(字面意思是比较与交换),JUC包中大量使用到了CAS,好比咱们的atomic包下的原子类就是基于CAS来实现。区别于悲观锁synchronized,CAS是乐观锁的一种实现,在某些场合使用它能够提升咱们的并发性能。java
在CAS中,主要是涉及到三个操做数,所期盼的旧值、当前工做内存中的值、要更新的值,仅当所期盼的旧值等于当前值时,才会去更新新值。编程
二:CAS举例
好比当以下场景,因为i++是个复合操做,读取、自增、赋值三步操做,所以在多线程条件下咱们须要保证i++操做的安全安全
public class CASTest { int i = 0; public void increment() { i++; } }
解决办法有经过使用synchronized来解决,synchronized解决了并发编程的原子性,可见性,有序性。多线程
public class CASTest { int i = 0; public synchronized void increment() { i++; } }
但synchronized毕竟是悲观锁,尽管它后续进行了若干优化,引入了锁的膨胀升级措施,可是仍是存在膨胀为重量级锁而致使阻塞问题,所以,咱们能够使用基于CAS实现的原子类AtomicInteger来保证其原子性并发
public class CASTest { AtomicInteger i = new AtomicInteger(0); public static void increment() { //自增并返回新值 i.incrementAndGet(); } }
三:CAS原理分析
atomic包下的原子类就是基于CAS实现的,咱们拿AtomicInteger来分析下CAS.ide
public class AtomicInteger extends Number implements java.io.Serializable { private static final long serialVersionUID = 6214790243416807050L; // CAS操做是基于一个Unsafe类,Unsafe类是整个Concurrent包的基础,里面全部的函数都是native的 private static final Unsafe unsafe = Unsafe.getUnsafe(); //内存偏移量 private static final long valueOffset; static { try { //初始化地址偏移量 valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } //底层采用volatile修饰值,保证其可见性和有序性 private volatile int value;
从AtomicInteger定义的相关属性来看,其内部的操做都是基于Unsafe类,由于在Java中,咱们并不能直接操做内存,可是Java仍是开放了一个Unsafe类来给咱们进行操做,顾名思义,Unsafe,是不安全的,所以要谨慎使用。函数
其内部定义的值是用volatiel进行修饰的,volatile能够保证有序性和可见性,具体为何能够保证就不在此阐述了。源码分析
再来看看其几个核心的APIpost
//以原子方式将值设置为给定的新值 expect:指望值 update:旧值 public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); } //以原子方式将当前值+1,返回指望值 public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; } //以原子方式将当前值-1,返回指望值 public final int decrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, -1) - 1; }
关于其源码仍是不多的,基本都是基于Unsafe类进行实现的。性能
先来看看compareAndSet方法,其调用的是Unsafe的compareAndSwapInt方法,当工做内存中的值与所期盼的旧值不相同的时候,会更新失败,举例说明:
public class CASDemo { public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(2020); System.out.println("更新结果:"+atomicInteger.compareAndSet(2020, 2021)); System.out.println("当前值为:"+atomicInteger.get()); //自增长一 atomicInteger.getAndIncrement(); System.out.println("更新结果:"+atomicInteger.compareAndSet(2020, 2021)); System.out.println("当前值为:"+atomicInteger.get()); } }
在来看看incrementAndGet方法,其调用的是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; }
四:CAS缺点分析
CAS的优势很明显,基于乐观锁的思想,提升了并发状况下的性能,缺点主要是ABA问题、自旋时间过长致使CPU占有率太高、只能保证一个共享变量的原子性。
ABA问题
就是一个值由A变为B,在由B变为A,使用CAS操做没法感知到该种状况下出现的变化,带来的后果很严重,好比银行内部员工,从系统挪走一百万,以后还了回来,系统感知不到岂不是要出事。模拟下出现ABA问题:public class ABA { private static AtomicInteger atomicInteger = new AtomicInteger(0); public static void main(String[] args) { //线程t1实现0->1->0 Thread t1 = new Thread(new Runnable() { @Override public void run() { atomicInteger.compareAndSet(0,1); atomicInteger.compareAndSet(1,0); } },"t1"); //线程t2实现0->100 Thread t2 = new Thread(new Runnable() { @Override public void run() { try { //模拟狸猫换太子行为 TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("更新结果:"+atomicInteger.compareAndSet(0, 100)); } }); t1.start(); t2.start(); } }
运行结果是:true
解决ABA能够使每一次修改都带上时间戳,以记录版本号的形式来使的CAS感知到这种狸猫换太子的操做。Java提供了AtomicStampedReference类来解决,该类除了指定旧值与期盼值,还要指定旧的版本号与期盼的版本号
public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair<V> current = pair; return expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp ==current.stamp) || casPair(current, Pair.of(newReference, newStamp))); }
public class ABA_Test { // 初始值100,版本号1 private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(100, 1); public static void main(String[] args) throws InterruptedException { // AtomicStampedReference实现 Thread tsf1 = new Thread(new Runnable() { @Override public void run() { try { // 让 tsf2先获取stamp,致使预期时间戳不一致 TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } // 预期引用:100,更新后的引用:110,预期标识getStamp() 更新后的标识getStamp() + 1 atomicStampedReference.compareAndSet(100, 110, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); atomicStampedReference.compareAndSet(110, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); } }); Thread tsf2 = new Thread(new Runnable() { @Override public void run() { int stamp = atomicStampedReference.getStamp(); try { TimeUnit.SECONDS.sleep(2); // 线程tsf1执行完 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println( "AtomicStampedReference:" + atomicStampedReference.compareAndSet(100, 120, stamp, stamp + 1)); } }); tsf1.start(); tsf2.start(); } }
运行结果:
自旋次数过长
CAS是基于乐观锁的思想实现的,当频繁出现当前值与所旧预期值不相等的状况,会致使频繁的自旋而使得浪费CPU资源。
只能保证单个共享变量的原子性
单纯对共享变量进行CAS操做,只能保证单个,没法使多个共享变量同时进行原子操做。