Java中CAS-ABA的问题解决方案

忻州SEO摘要

CAS即对比交换,它在保证数据原子性的前提下尽可能的减少了锁的使用,很多编程语言或者系统实现上都大量的使用了CAS。

 

了解CAS(Compare-And-Swap)

CAS即对比交换,它在保证数据原子性的前提下尽可能的减少了锁的使用,很多编程语言或者系统实现上都大量的使用了CAS。

JAVA中CAS的实现

JAVA中的cas主要使用的是Unsafe方法,Unsafe的CAS操作主要是基于硬件平台的汇编指令,目前的处理器基本都支持CAS,只不过不同的厂家的实现不一样罢了。

Unsafe提供了三个方法用于CAS操作,分别是

1
<p style= "line-height: 1.5em;" > public  final  native  boolean  compareAndSwapObject(Object value,  long  valueOffset, Object expect, Object update);<br> public  final  native  boolean  compareAndSwapInt(Object value,  long  valueOffset,  int  expect,  int  update);<br> public  final  native  boolean  compareAndSwapLong(Object value,  long  valueOffset,  long  expect,  long  update);<br></p>
  • value 表示 需要操作的对象

  • valueOffset 表示 对象(value)的地址的偏移量(通过Unsafe.objectFieldOffset(Field valueField)获取)

  • expect 表示更新时value的期待值

  • update 表示将要更新的值

具体过程为每次在执行CAS操作时,线程会根据valueOffset去内存中获取当前值去跟expect的值做对比如果一致则修改并返回true,如果不一致说明有别的线程也在修改此对象的值,则返回false

Unsafe类中compareAndSwapInt的具体实现:

1
<p style= "line-height: 1.5em;" >UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))<br>  UnsafeWrapper( "Unsafe_CompareAndSwapInt" );<br>  oop p = JNIHandles::resolve(obj);<br>  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);   return  (jint)(Atomic::cmpxchg(x, addr, e)) == e;<br>UNSAFE_END<br></p>

ABA问题

线程1准备用CAS修改变量值A,在此之前,其它线程将变量的值由A替换为B,又由B替换为A,然后线程1执行CAS时发现变量的值仍然为A,所以CAS成功。但实际上这时的现场已经和最初不同了。

aba.png

例子

1
<p style= "line-height: 1.5em;" > public  static  AtomicInteger a =  new  AtomicInteger( 1 ); public  static  void  main(String[] args){<br>    Thread main =  new  Thread(() -> {<br>        System.out.println( "操作线程"  + Thread.currentThread() + ",初始值 = "  + a);   //定义变量 a = 1<br>        try {<br>            Thread.sleep(1000);  //等待1秒 ,以便让干扰线程执行<br>        } catch (InterruptedException e) {<br>            e.printStackTrace();<br>        }        boolean isCASSuccess = a.compareAndSet(1,2); // CAS操作<br>        System.out.println("操作线程" + Thread.currentThread() +",CAS操作结果: " + isCASSuccess);<br>    },"主操作线程");<br><br>    Thread other = new Thread(() -> {<br>        Thread.yield();  //确保thread-main线程优先执行<br>        a.incrementAndGet(); // a 加 1, a + 1 = 1 + 1 = 2<br>        System.out.println("操作线程" + Thread.currentThread() +",【increment】 ,值 = "+ a);<br>        a.decrementAndGet(); // a 减 1, a - 1 = 2 - 1 = 1<br>        System.out.println("操作线程" + Thread.currentThread() +",【decrement】 ,值 = "+ a);<br>    },"干扰线程");<br><br>    main.start();<br>    other.start();<br>}<br></p>

输出> 操作线程Thread[主操作线程,5,main],初始值 = 1>
操作线程Thread[干扰线程,5,main],【increment】 ,值 = 2>
操作线程Thread[干扰线程,5,main],【decrement】 ,值 = 1>
操作线程Thread[主操作线程,5,main],CAS操作结果: true

解决ABA方案

思路

解决ABA最简单的方案就是给值加一个修改版本号,每次值变化,都会修改它版本号,CAS操作时都对比此版本号。

aba_2.png

JAVA中ABA中解决方案(AtomicStampedReference)

AtomicStampedReference主要维护包含一个对象引用以及一个可以自动更新的整数"stamp"的pair对象来解决ABA问题。

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
//关键代码
public  class  AtomicStampedReference<V> {
     private  static  class  Pair<T> {
         final  T reference;   //维护对象引用
         final  int  stamp;   //用于标志版本
         private  Pair(T reference,  int  stamp) {
             this .reference = reference;
             this .stamp = stamp;
         }
         static  <T> Pair<T> of(T reference,  int  stamp) {
             return  new  Pair<T>(reference, stamp);
         }
     }
     private  volatile  Pair<V> pair;
     ....
     /**
       * expectedReference :更新之前的原始值
       * newReference : 将要更新的新值
       * expectedStamp : 期待更新的标志版本
       * newStamp : 将要更新的标志版本
       */
     public  boolean  compareAndSet(V   expectedReference,
                                  V   newReference,
                                  int  expectedStamp,
                                  int  newStamp) {
         Pair<V> current = pair;  //获取当前pair
         return
             expectedReference == current.reference &&  //原始值等于当前pair的值引用,说明值未变化
             expectedStamp == current.stamp &&  // 原始标记版本等于当前pair的标记版本,说明标记未变化
             ((newReference == current.reference &&
               newStamp == current.stamp) ||  // 将要更新的值和标记都没有变化
              casPair(current, Pair.of(newReference, newStamp)));  // cas 更新pair
     }
}

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private  static  AtomicStampedReference<Integer> atomicStampedRef =
         new  AtomicStampedReference<>( 1 0 );
public  static  void  main(String[] args){
     Thread main =  new  Thread(() -> {
         System.out.println( "操作线程"  + Thread.currentThread() + ",初始值 a = "  + atomicStampedRef.getReference());
         int  stamp = atomicStampedRef.getStamp();  //获取当前标识别
         try  {
             Thread.sleep( 1000 );  //等待1秒 ,以便让干扰线程执行
         catch  (InterruptedException e) {
             e.printStackTrace();
         }
         boolean  isCASSuccess = atomicStampedRef.compareAndSet( 1 , 2 ,stamp,stamp + 1 );   //此时expectedReference未发生改变,但是stamp已经被修改了,所以CAS失败
         System.out.println( "操作线程"  + Thread.currentThread() + ",CAS操作结果: "  + isCASSuccess);
     }, "主操作线程" );
     Thread other =  new  Thread(() -> {
         Thread.yield();  // 确保thread-main 优先执行
atomicStampedRef.compareAndSet( 1 , 2 ,atomicStampedRef.getStamp(),atomicStampedRef.getStamp() + 1 );
         System.out.println( "操作线程"  + Thread.currentThread() + ",【increment】 ,值 = " + atomicStampedRef.getReference());
         atomicStampedRef.compareAndSet( 2 , 1 ,atomicStampedRef.getStamp(),atomicStampedRef.getStamp() + 1 );
         System.out.println( "操作线程"  + Thread.currentThread() + ",【decrement】 ,值 = " + atomicStampedRef.getReference());
     }, "干扰线程" );
     main.start();
     other.start();
}

// 输出

> 操作线程Thread[主操作线程,5,main],初始值 a = 2

> 操作线程Thread[干扰线程,5,main],【increment】 ,值 = 2

> 操作线程Thread[干扰线程,5,main],【decrement】 ,值 = 1

> 操作线程Thread[主操作线程,5,main],CAS操作结果: false

来源:内蒙古SEO

猜你喜欢

转载自www.cnblogs.com/1994jinnan/p/11985687.html