CAS
CAS是指Compare And Swap
,比较并交换,是一种很重要的同步思想。如果主内存的值跟期望值一样,那么就进行修改,否则一直重试,直到一致为止。
1 2 3 4 5 6 7 8 9 10 11 12 13 import java.util.concurrent.atomic.AtomicInteger; public class CASDemo { public static void main (String [] args) { AtomicInteger atomicInteger = new AtomicInteger(5 ); System.out.println (atomicInteger.compareAndSet(5 ,2019 )+"\t current data: " +atomicInteger.get ()); System.out.println (atomicInteger.compareAndSet(5 ,1024 )+"\t current data: " +atomicInteger.get ()); } }
CAS底层原理 1 2 3 4 5 6 7 8 9 10 11 public final int getAndIncrement() { return unsafe.getAndAddInt(this ,valueOffset ,1) ; } public final int getAnddAddInt(Object var1 ,long var2 ,int var4 ) { int var5; do { var5 = this.getIntVolatile(var1 , var2 ) ; } while (!this.compareAndSwapInt(var1 , var2 , var5 , var5 + var4 ) ); return var5; }
this.getIntVolatile 获取主内存变量值,是个 native 方法,其目的是获取 var1 在 var2 偏移量的值,其中 var1 就是 AtomicInteger, var2 是 valueOffset 值。
那么 CAS 核心重点来了,compareAndSwapInt 就是实现 CAS 的核心方法,其原理是如果 var1 中的 value 值和 var5 相等,就证明没有其他线程改变过这个变量,那么就把 value 值更新为 var5 + var4,其中 var4 是更新的增量值,反之则继续以自旋方式继续进行操作
Unsafe 是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法。
注意Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务。
2.变量valueOffset,表示该变量在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的。
3.变量value用volatile修饰,保证了多线程之间的内存可见性。
CAS缺点 CAS实际上是一种自旋锁
一直循环,开销比较大。
只能保证一个变量的原子操作,多个变量依然要加锁。 当对一个共享变量执行操作时,我们只能使用循环CAS的方式来保证原子操作,但是,对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。
ABA问题
ABA问题 CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。这就是CAS的ABA问题。
常见的解决思路是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。这就引出了AtomicReference
原子引用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicStampedReference; public class ABADemo { static AtomicReference<Integer> atomicReference = new AtomicReference<>(100 ); static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100 ,1 ); public static void main(String[] args){ 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) +"\t" +atomicReference.get() ); },"t2" ).start() ; try {TimeUnit .SECONDS . sleep(2 );} catch (InterruptedException e) {e.printStackTrace() ;} System . out.println("======以下是ABA问题的解决=====" ); new Thread(() ->{ int stamp = atomicStampedReference.getStamp() ; System . out.println(Thread . currentThread() .getName() +"\t第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() +"\t第2次版本号:" +atomicStampedReference.getStamp() ); atomicStampedReference.compareAndSet(101,100,atomicStampedReference .getStamp () ,atomicStampedReference.getStamp() +1 ); System . out.println(Thread . currentThread() .getName() +"\t第3次版本号:" +atomicStampedReference.getStamp() ); },"t3" ).start() ; new Thread(() ->{ int stamp = atomicStampedReference.getStamp() ; System . out.println(Thread . currentThread() .getName() +"\t第1次版本号:" +stamp); try {TimeUnit .SECONDS . sleep(3 );} catch (InterruptedException e) {e.printStackTrace() ;} boolean result = atomicStampedReference.compareAndSet(100,2019,stamp ,atomicStampedReference .getStamp () +1 ); System . out.println(Thread . currentThread() .getName() +"\t修改成功否: " +result+"\t当前最新实际版本号:" +atomicStampedReference.getStamp() ); },"t4" ).start() ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import javax.jws.soap.SOAPBinding;import java.util.concurrent.atomic.AtomicInteger;import java.util.concurrent.atomic.AtomicReference;class User { String userName; int age; public User (String userName, int age) { this .userName = userName; this .age = age; } } public class AtomicReferenceDemo { public static void main (String [] args) { AtomicReference<User> atomicReference = new AtomicReference<>(); User z3 = new User("z3" ,22 ); User li4 = new User("li4" ,25 ); atomicReference.set (z3); z3 = new User("wang5" , 44 ); System.out.println (atomicReference.compareAndSet(z3,li4)+"\t" +atomicReference.get ().toString()); } }