美文网首页
java.util.concurrent.atomic源码学习(

java.util.concurrent.atomic源码学习(

作者: 果拉图 | 来源:发表于2019-08-15 21:17 被阅读0次

1.常用类概览

先看一下atomic主要包含哪些常用类


atomic主要类

2.原子类&原子数组

在介绍原子类&原子数组前,首先要引入两个重要的unsafe方法,分别是

/**
获得某个属性域在这个类的内存偏移,入参是一个Filed
由于底层方法直接操作内存,因此对于属性域在内存的位置是必要信息,在后续getAndSet操作以及CAS操作时都会用到
*/
unsafe.objectFieldOffset(Field var1); 
/*这几个都是CAS的核心用法,入参分别是
1.待修改对象
2.属性域在这个对象的内存偏移量
3.excpect值
4.update值
CAS对比的时候就是比较寄存器的值是否等于excpect的值,是的话将其更新为update值
*/
unsafe.compareAndSwapLong(Object var1, long var2, long var4, long var6);
unsafe.compareAndSwapInt(Object var1, long var2, int var4, int var5);
unsafe.compareAndSwapObject(Object var1, long var2, Object var4, Object var5));

2.1 原子类

知道了这些底层的基本方法,这些类的实现也就显而易见了,常见的套路是在对应的AtomicInteger,AtomicLong,AtomicReference开辟一个私有的value域,存放这个初始值,同时持有一个final long类型的valueOffset存在这个value域在AtomicXXX中的内存偏移,这个value必须是volatile方便更新后内存可见。一般会有如下的代码

 static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicReference.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

可以看到,后来陆续的原子操作都是基于这个AtomicXXX本体对象(内存基础地址)和valueOffset(偏移地址)定位到变量的绝对地址,直接用底层方法对内存进行各种操作,以AtomicReference为例

//延迟set
public final void lazySet(V newValue) {
      unsafe.putOrderedObject(this, valueOffset, newValue);
}
//调用底层方法
public final boolean compareAndSet(V expect, V update) {
      return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
 }

public final V getAndSet(V newValue) {
      return (V)unsafe.getAndSetObject(this, valueOffset, newValue);
}
/**
* 支持UnaryOperator操作符,这个是1.8新加的方法,该Function接收一个Reference类型的
* 入参(也就是old值) 经过该函数操作后获得新值,同时保持操作的原子性
* getAndUpdate和updateAndGet两个方法很有趣,一个返回prev,一个返回next
*/
 public final V getAndUpdate(UnaryOperator<V> updateFunction) {
       V prev, next;
       do {
           prev = get();
           next = updateFunction.apply(prev);
      } while (!compareAndSet(prev, next));
      return prev;
 }

 public final V updateAndGet(UnaryOperator<V> updateFunction) {
      V prev, next;
      do {
          prev = get();
          next = updateFunction.apply(prev);
      } while (!compareAndSet(prev, next));
      return next;
}
/**
* 双目运算符也是可以接受的
**/
 public final V getAndAccumulate(V x, BinaryOperator<V> accumulatorFunction) {
      V prev, next;
      do {
          prev = get();
          next = accumulatorFunction.apply(prev, x);
       } while (!compareAndSet(prev, next));
      return prev;
 }

 public final V accumulateAndGet(V x, BinaryOperator<V> accumulatorFunction) {
      V prev, next;
      do {
          prev = get();
          next = accumulatorFunction.apply(prev, x);
      } while (!compareAndSet(prev, next));
      return next;
 }

2.1 原子数组

看完了AtomicXXX之后,AtomicXXXArray类就容易很多了,核心的CAS的套路都是一样的,稍有不同的就是如何获取待修改对象valueOffset的值,因为不同于AtomicXXX类,数组存放了一堆数据而不是单个,方法在于这个

//数组的基地址,第一个元素的内存位置,除去对象头后的地址
private static final int base = unsafe.arrayBaseOffset(int[].class);
//数组的元素偏移量,取决于什么元素类型的数组,如果是int为4,long为8
private static final int shift;

static {
    //对于int来说scale为16
    int scale = unsafe.arrayIndexScale(int[].class);
    if ((scale & (scale - 1)) != 0)
        throw new Error("data type scale not a power of two");
   //4位偏移量,即每个元素占据4bit的长度,也就是int的长度
    shift = 31 - Integer.numberOfLeadingZeros(scale);
}

//这个是实际计算对于index为i的元素在数组中的内存位置
private long checkedByteOffset(int i) {
    if (i < 0 || i >= array.length)
        throw new IndexOutOfBoundsException("index " + i);

    return byteOffset(i);
}
//base的内存位置+ index * 每个元素的位数(4)= index位置的元素的内存位置
private static long byteOffset(int i) {
    return ((long) i << shift) + base;
}

找到index的元素对应的内存位置后,剩下的工作和思路就完全一样的啦!!注意的是数组操作会多一个下标表示操作的元素

public final boolean compareAndSet(int i, int expect, int update) {
    return compareAndSetRaw(checkedByteOffset(i), expect, update);
}

public final int getAndUpdate(int i, IntUnaryOperator updateFunction) {
    long offset = checkedByteOffset(i);
    int prev, next;
    do {
        prev = getRaw(offset);
        next = updateFunction.applyAsInt(prev);
    } while (!compareAndSetRaw(offset, prev, next));
    return prev;
}

public final int updateAndGet(int i, IntUnaryOperator updateFunction) {
    long offset = checkedByteOffset(i);
    int prev, next;
    do {
        prev = getRaw(offset);
        next = updateFunction.applyAsInt(prev);
    } while (!compareAndSetRaw(offset, prev, next));
    return next;
}

3 带计数器的原子类

CAS虽然能够保证最终一致性,但它解决不了ABA问题,特别是对于引用类型,CAS只是保证这个引用句柄(相当于指针)更新是原子的,至于引用从A换成B又换成A,这个过程A指向的对象是不是发生了变化,CAS也无法感知,甚至连ABA这样的变化也感知不到,那么如何响应ABA类型的变化呢,AtomicStampedReference和AtomicMarkableReference为我们带来了答案——既然单个value的持有区分不开ABA场景,那么我们就在这个vaule旁边加一个计数器吧,这样一旦value出现ABA的变更情况,计数器会+2,此时修改后的A和未修改的A就能区分了。

//多了一个内部类,Pair对,分别存储待修改的reference和对此reference修改计数的计数器(戳)
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);
    }
}
//对于value从单一的reference变成pair,所有的修改需要同时修改pair(reference + stamp)
private volatile Pair<V> pair;

/**
** compareAndSet除了需要传递待更新的reference,还需要传入正确的stamp
**/
public boolean compareAndSet(V   expectedReference,
                             V   newReference,
                             int expectedStamp,
                             int newStamp) {
    Pair<V> current = pair;
    return
        expectedReference == current.reference &&
        expectedStamp == current.stamp &&
        ((newReference == current.reference &&
          newStamp == current.stamp) ||
         casPair(current, Pair.of(newReference, newStamp)));
}
/**
** 计数器的存在使得在使用过程中可能发生漂移(即stamp不知道加到哪里去了),
** 这个时候可以通过这个方法进行校正
**/
public boolean attemptStamp(V expecdReference, int newStamp) {
    Pair<V> current = pair;
    return
        expectedReference == current.reference &&
        (newStamp == current.stamp ||
         casPair(current, Pair.of(expectedReference, newStamp)));
}

//最后是我们的老朋友unsafe基本类以及获取内存偏移的方法
private static final long pairOffset =
    objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class);

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

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;
    }
}

至此atomic包的源代码我们已经分析过半,AtomicXXX提供了一套完整的原子变量的方案实现安全并发。可以对于当初程序中写的普通变量,要想实现安全并发的话,改源代码也不好改,同时可能开销也比较大,有其他方法可以实现类似的功能吗,答案是有的,就是下节我们需要介绍的AtomicXXXFieldUpdater类,基于反射的方式实现原子更新。

相关文章

网友评论

      本文标题:java.util.concurrent.atomic源码学习(

      本文链接:https://www.haomeiwen.com/subject/rmvtsctx.html