一、什么是CAS?
比较并交换(compare and swap),它是一条CPU并发原语。判断内存某个位置的值是否为预期值,如果是的话则更新为新值,这个过程是原子的。
原语是操作系统的用语,是由若干条指定组成,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。
public class Solution { public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(5); System.out.println(atomicInteger.compareAndSet(5,22)+ ". current data is: " + atomicInteger.get()); System.out.println(atomicInteger.compareAndSet(5,33)+ ". current data is: " + atomicInteger.get()); } }
运行结果:
true. current data is: 22 false. current data is: 22
public class Solution { public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(5); System.out.println(atomicInteger.compareAndSet(5,22)+ ". current data is: " + atomicInteger.get()); System.out.println(atomicInteger.compareAndSet(22,33)+ ". current data is: " + atomicInteger.get()); } }
运行结果:
true. current data is: 22 true. current data is: 33
结果说明:
1、第一次运行,由于在物理内存中的值是5,所以符合expected的值,就修改为了22,而第二条语句的expected为5,但是现在值已经为22,所以不符合预期,就不能改变值。
2、第二次运行,每次的expected的值都为物理内存中的值,所以都进行了修改。
二、CAS底层原理,什么是UnSafe?
要点:UnSafe类(存在rt.jar中)+CAS自旋锁
AtomicInteger的源码
public class AtomicInteger extends Number implements java.io.Serializable { private static final long serialVersionUID = 6214790243416807050L; // setup to use Unsafe.compareAndSwapInt for updates 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); } } private volatile int value; }
1、Unsafe类
这是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,UnSafe相当于一个后门,基于该类可以直接操作特定内存的数据
UnSafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因此Java中CAS操作执行依赖于UnSafe类
UnSafe类中的所有方法都是native修饰的,也就是说UnSafe类中的方法都直接调用操作系统底层资源执行相应任务
2、变量valueOffset,表示该变量在内存中的偏移地址,因为UnSafe就是根据内存偏移地址获取数据的。
//this:当前对象 //valueOffset:内存偏移量 public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } //var1 AtomicInteger对象本身 //var2 该对象值的引用地址 //var4 需要变动的值 //var5 用var1 var2找出主内存中真实的值 //用该对象当前值与var5比较,如果相同,更新var5+var4返回true,如果不同,继续取值比较,直到更新完成 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; }
总结:getAndIncrement()底层调用unsafe类方法,传入三个参数,unsafe.getAndAddInt()底层使用CAS思想,如果比较成功加1,如果比较失败则重新获得,再比较,直到成功
3、变量value用volatile修饰,保证了多线程之间的内存可见性
二、CAS缺点?
1、循环时间开销大(如果CAS失败,会一直尝试)
2、只能保证一个共享变量的原子操作。(对多个共享变量操作时,循环CAS无法保证操作的原子性,只能用加锁来保证)
3、存在ABA问题
四、原子类AtomicInteger类ABA问题及解决方案
1、ABA问题是怎么产生的?
当第一个线程执行CAS(V,E,U)操作,在获取到当前变量V,准备修改为新的值U之前。另外两个线程已经连续修改了两次变量V的值,使得该值又恢复为旧的值,这样我们就无法正确判断这个变量是否已经被修改过
2、ABA问题的解决方案:
AtomicStampedReference:是一个带有时间戳的对象引用,在每次修改后,不仅会设置新的值,还会记录修改的时间
AtomicMarkableReference:维护的是一个Boolean值的标识,这种方式并不能完全防止ABA问题的发生,只能减少ABA发生的概率
public class Solution { static AtomicReference<Integer> atomicReference = new AtomicReference<>(100); static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1); public static void main(String[] args) throws InterruptedException { System.out.println("========ABA问题的产生========="); new Thread(() -> { atomicReference.compareAndSet(100, 101); atomicReference.compareAndSet(101, 100); }, "t1").start(); new Thread(() -> { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(atomicReference.compareAndSet(100, 2019) + " " + atomicReference.get()); }, "t2").start(); TimeUnit.SECONDS.sleep(2); System.out.println("========ABA问题的解决========="); new Thread(() -> { int stamp = atomicStampedReference.getStamp(); System.out.println(Thread.currentThread().getName() + "线程第1次版本号:" + stamp); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); System.out.println(Thread.currentThread().getName() + "线程第2次版本号:" + atomicStampedReference.getStamp()); atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); System.out.println(Thread.currentThread().getName() + "线程第3次版本号:" + atomicStampedReference.getStamp()); }, "t3").start(); new Thread(() -> { int stamp = atomicStampedReference.getStamp(); System.out.println(Thread.currentThread().getName() + "线程第1次版本号:" + stamp); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1); System.out.println(Thread.currentThread().getName() + "修改成功否:" + result + " 当前最新版本号:" + atomicStampedReference.getStamp()); System.out.println(Thread.currentThread().getName() + "当前最新值:" + atomicStampedReference.getReference()); }, "t4").start(); } }
运行结果:
========ABA问题的产生========= true 2019 ========ABA问题的解决========= t3线程第1次版本号:1 t4线程第1次版本号:1 t3线程第2次版本号:2 t3线程第3次版本号:3 t4修改成功否:false 当前最新版本号:3 t4当前最新值:100
五、原子更新引用
class User { User(String name, int age) { this.name = name; this.age = age; } private String name; private int age; } public class Solution { static AtomicReference<Integer> atomicReference = new AtomicReference<>(100); static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1); public static void main(String[] args) throws InterruptedException { AtomicReference<User> atomicReference = new AtomicReference<>(); User user = new User("monster", 18); User updateUser = new User("jack", 25); atomicReference.set(user); System.out.println(atomicReference.get()); atomicReference.compareAndSet(user, updateUser); System.out.println(atomicReference.get()); } }
运行结果:
User@74a14482
User@1540e19d