AtomicStampedReference和AtomicMarkableReference详解

Posted Dream_it_possible!

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AtomicStampedReference和AtomicMarkableReference详解相关的知识,希望对你有一定的参考价值。

 

目录

AtomicStampedReference

Pair

获取当前reference值和版本号stamp

CAS操作

unsafe

完整案例

AtomicMarkableReference


      AtomicStampedReference和AtomicMarkableReference是解决CAS存在ABA问题的两种方案,他们俩的实现原理大致相同,弄明白AtomicStampedReference的实现原理,AtomicMarkableReference就迎刃而解了。

AtomicStampedReference

AtomicStampedReference是JUC的atomic包里提供的一个类,官方解释该类的作用是:

An AtomicStampedReference maintains an object reference along with an integer "stamp", that can be updated atomically.
Implementation note: This implementation maintains stamped references by creating internal objects representing "boxed" [reference, integer] pairs.

意思是: AtomicStampedReference维护了一个reference对象和一个int类型的stamp, 能提供原子性更新的功能。

具体的原理: 每次CAS前,先获取当AtomicStampedReference对象里的Pair里的stamp和reference, 该stamp为版本号,reference为期望的对象值,如果获取到的版本号为期望的版本号并且获取到的reference值为期望值,就是说当前线程获取到的版本号在CAS前没有被其他线程修改且reference也没有修改,那么当前线程才能进行CAS操作,如果版本号不是期望值,那么就不执行更新操作。

Pair

        Pair是AtomicStampedReference和AtomicMarkableReference类里维护的一个静态内部类。

   private static class Pair<T> 
       // 存放的对象,采用泛型
        final T reference;
      // stamp类似于版本号的功能
        final int stamp;
        private Pair(T reference, int stamp) 
            this.reference = reference;
            this.stamp = stamp;
        
       //提供初始化Pair对象的方法
        static <T> Pair<T> of(T reference, int stamp) 
            return new Pair<T>(reference, stamp);
        
    

         AtomicStampedReference类和AtomicMarkableReference类里都含有一个volatile修饰的Pair成员变量,它的作用是用来存放当前的Pair值。

    private volatile Pair<V> pair;

        因为CAS操作时,我们需要用expected值与当前值做比较。

获取当前reference值和版本号stamp

        给一个数组容量为1的数组给get()方法作为参数,版本号会存放到数组里,返回值为当前的reference值

int[] stampHolder = new int[1];
// 1. 给一个空数组给atomicStampedReference
int value = (int) atomicStampedReference.get(stampHolder);

        就是把当前的pair值给拿出来,然后取出stamp赋值给数组并将reference进行返回即可。

  public V get(int[] stampHolder) 
     // 获取当前pair对象
        Pair<V> pair = this.pair;
      // 赋值
        stampHolder[0] = pair.stamp;
        return pair.reference;
    

CAS操作

CAS操作有4个参数:

   public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) 
        Pair<V> current = pair;
        return
           // 比较期望值
            expectedReference == current.reference &&
            expectedStamp == current.stamp &&
         // 如果当前值与新值相等并且版本号一致,那么直接return, 不执行cas操作
            ((newReference == current.reference &&
              newStamp == current.stamp) ||
          // 执行CAS操作
             casPair(current, Pair.of(newReference, newStamp)));
    

        casPair方法的底层就是调用的unsafe提供的compareAndSwapObject方法。

    private boolean casPair(Pair<V> cmp, Pair<V> val) 
        return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
    

unsafe

        第一步获取到unsafe就能执行CAS操作,获取到unsafe变量

    private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe();

         第二步获取到共享变量pair的偏移量

 private static final long pairOffset =
        objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class);

        根据UNSAFE对象和字段field获取到字段在对象里的偏移量, 偏移量可以用sun.misc.Unsafe类里提供一个native方法objectFieldOffset(Field field)方法来获取。

 static long objectFieldOffset(sun.misc.Unsafe UNSAFE,
                                  String field, Class<?> klazz) 
        try 
            return UNSAFE.objectFieldOffset(klazz.getDeclaredField(field));
         catch (NoSuchFieldException e) 
            // Convert Exception to corresponding Error
            NoSuchFieldError error = new NoSuchFieldError(field);
            error.initCause(e);
            throw error;
        
    

完整案例

        用AtomicStampedReference初始化当前reference值为1,版本号为1吗,定义2个线程,线程1的目标是将reference值更新为3, 线程2的目标是将reference值更新为2,那么如果2个线程同时更新,那么必定有其中1个线程更新失败,因为另外一个线程更改了版本号和reference值。

package com.example.jucdemo.atomic;

import java.util.concurrent.atomic.AtomicStampedReference;

import lombok.extern.slf4j.Slf4j;


@Slf4j
public class AtomicStampedReferenceTest 

    public static void main(String[] args) 
        // 定义AtomicStampedReference, 版本号为1, 为了方便对象的值为1,Pair.reference值为1, Pair.stamp为1
        AtomicStampedReference atomicStampedReference = new AtomicStampedReference(1,1);

        new Thread(()->
            int[] stampHolder = new int[1];
            // 1. 给一个空数组给atomicStampedReference
            int reference = (int) atomicStampedReference.get(stampHolder);
            int stamp = stampHolder[0];
            // 2. 获取到当前版本号,值为stampHolder[0]
            log.debug("Thread1 read value: " + reference + ", stamp: " + stamp);
            // 阻塞1s
            try 
                Thread.sleep(1000);
             catch (InterruptedException e) 
                e.printStackTrace();
            
            // Thread1通过CAS操作修改reference值为3,每次修改可以通过stamp+1保证版本唯一性
            if (atomicStampedReference.compareAndSet(reference, 3,stamp,stamp+1)) 
                log.debug("Thread1 update from " + reference + " to 3");
                // do something
             else 
                log.debug("Thread1 update fail!");
            
        ,"Thread1").start();

        new Thread(()->
            int[] stampHolder = new int[1];
            int reference = (int)atomicStampedReference.get(stampHolder);
            int stamp = stampHolder[0];
            log.debug("Thread2 read value: " + reference+ ", stamp: " + stamp);
            // Thread2通过CAS修改reference值为2
            if (atomicStampedReference.compareAndSet(reference, 2,stamp,stamp+1)) 
                log.debug("Thread2 update from " + reference + " to 2");
                // do something
                reference = (int) atomicStampedReference.get(stampHolder);
                stamp = stampHolder[0];
                log.debug("Thread2 read value: " + reference+ ", stamp: " + stamp);
                // Thread2通过CAS修改value值为1
                if (atomicStampedReference.compareAndSet(reference, 1,stamp,stamp+1)) 
                    log.debug("Thread2 update from " + reference + " to 1");
                
            
        ,"Thread2").start();
    

打印结果:

22:58:56.548 [Thread1] DEBUG com.example.jucdemo.atomic.AtomicStampedReferenceTest - Thread1 read value: 1, stamp: 1
22:58:56.548 [Thread2] DEBUG com.example.jucdemo.atomic.AtomicStampedReferenceTest - Thread2 read value: 1, stamp: 1
22:58:56.550 [Thread2] DEBUG com.example.jucdemo.atomic.AtomicStampedReferenceTest - Thread2 update from 1 to 2
22:58:56.550 [Thread2] DEBUG com.example.jucdemo.atomic.AtomicStampedReferenceTest - Thread2 read value: 2, stamp: 2
22:58:56.550 [Thread2] DEBUG com.example.jucdemo.atomic.AtomicStampedReferenceTest - Thread2 update from 2 to 1
22:58:57.551 [Thread1] DEBUG com.example.jucdemo.atomic.AtomicStampedReferenceTest - Thread1 update fail!

Process finished with exit code 0

 由打印结果可知,线程1更新失败,因为线程1在执行CAS前有个sleep操作,那么线程2会先去更新reference的值为2,版本号为2,当线程1重新唤醒执行的时候,会发现版本号发生变化了,那么CAS操作就不会进行了!

AtomicMarkableReference

        AtomicMarkableReference与AtomicStampedReference的区别是Pair内部类维护的类型不同。


    private static class Pair<T> 
        final T reference;
        final boolean mark;
        private Pair(T reference, boolean mark) 
            this.reference = reference;
            this.mark = mark;
        
        static <T> Pair<T> of(T reference, boolean mark) 
            return new Pair<T>(reference, mark);
        
    

        采用boolean类型的值替换掉了int stamp, 其他功能的实现方式参考AtomicStampedReference即可。

以上是关于AtomicStampedReference和AtomicMarkableReference详解的主要内容,如果未能解决你的问题,请参考以下文章

AtomicStampedReference

4.4.4 无锁的对象引用:AtomicReference和AtomicStampedReference

Java底层类和源码分析系列-AtomicStampedReference解决ABA问题

用ATOMICSTAMPEDREFERENCE解决ABA问题

用AtomicStampedReference解决ABA问题

CAS乐观锁使用AtomicStampedReference版本号控制手动实现原子计数