CAS 方式为乐观锁,synchronized 为悲观锁。因此使用 CAS 解决并发问题通常情况下性能更优。
但使用 CAS 方式也会有几个问题:
因为CAS需要在操作值的时候,检查值有没有发生变化,比如没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时则会发现它的值没有发生变化,但是实际上却变化了。
ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加1,那么A->B->A就会变成1A->2B->3A。
从Java 1.5开始,JDK的Atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
ABA问题可以通过对数据添加版本号的方式进行解决
AtomicStampedReference解决ABA问题
AtomicStampedReference主要维护包含一个对象引用以及一个可以自动更新的整数"stamp"的pair对象来解决ABA问题。
自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁。
还有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如,有两个共享变量i = 2,j = a,合并一下ij = 2a,然后用CAS来操作ij。
从Java 1.5开始,JDK提供了AtomicReference类来保证引用对象之间的原子性,就可以把多个变量放在一个对象里来进行CAS操作。
java中为我们提供了AtomicXXX 原子类(底层基于CAS进行更新数据的),不需要加锁就在多线程并发场景下实现数据的一致性。
public class CASDemo{ public static void main(string[] args){ AtomicInteger atomicInteger = new AtomicInteger(5);// mian do thing. . . . .. System.out.println(atomicInteger.compareAndSet(5, 2019)+"\t current data: "+atomicInteger.get()); System.out.println(atomicInteger.compareAndset(5, 1024)+"\t current data: "+atomicInteger.get()); } }
true 2019 false 2019
atomicInteger.getAndIncrement()源码(此方法实现原子的i++操作)
public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); //当前对象,内存地址偏移量,操作数 }
看出此方法是调用了unsafe类中的方法
由于Java方法无法直接访问底层系统,需要通过本地(Native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定的内存数据。
Unsafe类功能
Unsafe类中关于CAS的方法
public final int getAndAddInt(Object paramObject, long paramLong, int paramInt) { int i; do i = getIntVolatile(paramObject, paramLong); while (!compareAndSwapInt(paramObject, paramLong, i, i + paramInt)); return i; } public final long getAndAddLong(Object paramObject, long paramLong1, long paramLong2) { long l; do l = getLongVolatile(paramObject, paramLong1); while (!compareAndSwapLong(paramObject, paramLong1, l, l + paramLong2)); return l; } public final int getAndSetInt(Object paramObject, long paramLong, int paramInt) { int i; do i = getIntVolatile(paramObject, paramLong); while (!compareAndSwapInt(paramObject, paramLong, i, paramInt)); return i; } public final long getAndSetLong(Object paramObject, long paramLong1, long paramLong2) { long l; do l = getLongVolatile(paramObject, paramLong1); while (!compareAndSwapLong(paramObject, paramLong1, l, paramLong2)); return l; } public final Object getAndSetObject(Object paramObject1, long paramLong, Object paramObject2) { Object localObject; do localObject = getObjectVolatile(paramObject1, paramLong); while (!compareAndSwapObject(paramObject1, paramLong, localObject, paramObject2)); return localObject; }
从源码中发现,内部使用自旋的方式进行CAS更新(while循环进行CAS更新,如果更新失败,则循环再次重试)。
又从Unsafe类中发现,原子操作其实只支持下面三个方法。
public final native boolean compareAndSwapObject(Object paramObject1, long paramLong, Object paramObject2, Object paramObject3); public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2); public final native boolean compareAndSwapLong(Object paramObject, long paramLong1, long paramLong2, long paramLong3);
使用原子的方式更新基本类型,Atomic包提供了以下3个类。
通过原子的方式更新数组里的某个元素,Atomic包提供了以下的4个类:
Atomic包提供了以下三个类:
Atomic包提供了四个类进行原子字段更新: