并发编程笔记——第四章 Java并发包中原子操作类原理剖析

JUC包提供了一系列的原子性操作类(原理大致相同),这些类都是使用非阻塞算法CAS实现的,相比使用锁实现原子性操作者在性能上有很大提高。

一、原子变量操作类AtomicLong

public class AtomicLong extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 1927816293512124184L;

    // (1)获取Unsafe实例
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    // (2)存放变量value的偏移量
    private static final long valueOffset;
    // (3)判断JVM是否支持Long类型无所CAS
    static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8();
    private static native boolean VMSupportsCS8();

    static {
        try {
            // (4)获取value在AtomicLong中的偏移量
            valueOffset = unsafe.objectFieldOffset
                (AtomicLong.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
    
    // (5)实际变量值
    private volatile long value;

    public AtomicLong(long initialValue) {
        value = initialValue;
    }
    ...

AtomicLong类也在rt.jar包下,该类就是通过BootStrap类加载器进行加载的

    // 相当于CAS的i++
    public final long getAndIncrement() {
        return unsafe.getAndAddLong(this, valueOffset, 1L);
    }
public final class Unsafe {
    ...
    public final long getAndAddLong(Object var1, long var2, long var4) {
        long var6;
        do {
            var6 = this.getLongVolatile(var1, var2);
        } while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4)); // 无限重试,保证所有线程更新变量成功

        return var6;
    }

二、JDK 8新增的原子操作类LongAdder

  1. AtomicLong:提供CAS非阻塞原子性操作,比阻塞算法的同步器性能高。高并发下大量线程会同时竞争更新同个原子变量,由于同时只有一个线程的CAS操作会成功,会造成大量线程竞争失败后,通过无限循环不断进行自旋尝试CAS操作,白白浪费CPU资源。
  2. LongAdder:JDK 8新增,原子性递增递减类。把一个变量分解为多个变量,多个线程竞争多个资源。解决AtomicLong由于过多线程同时竞争同一个变量的更新而产生的性能瓶颈。

  • 使用LongAdder时,是在内部维护多个Cell变量,每个Cell里有一个初始值位0的long型变量。减少争夺共享资源的并发量,且线程争夺Cell失败后不会在同一个变量上自旋CAS操作,而是尝试在其他Cell上进行CAS操作。在获取LongAdder当前值时,是把所有Cell变量的value加上base返回的。
  • Cell类型是AtomicLong的一个改进,用来减少缓存的争用,也就是解决伪共享问题。

代码分析

围绕以下几个话题分析LongAdder的实现:

  1. LongAdder的结构:下图
  2. 当前线程应该访问Cell数组哪一个元素:add方法
  3. 如何初始化Cell数组:longAccumulate方法
  4. Cell数组如何扩容:longAccumulate方法
  5. 线程访问分配的Cell元素有冲突后如何处理:longAccumulate方法
  6. 如何保证线程操作被分配的Cell元素的原子性:cas方法
  • LongAdder继承Striped64,LongAdder的值为base与cell数组所有元素的value之和;cellBusy用来实现自旋锁,状态只有0,1,创建Cell元素、扩容Cell数组或初始化Cell数组时,使用CAS操作保证同时只有一个线程可以进行其中之一的操作

  • Cell的构造与cas方法
    @sun.misc.Contended static final class Cell {
        volatile long value;
        Cell(long x) { value = x; }
        final boolean cas(long cmp, long val) {
            return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
        }

        private static final sun.misc.Unsafe UNSAFE;
        private static final long valueOffset;
        static {
            try {
                UNSAFE = sun.misc.Unsafe.getUnsafe();
                Class<?> ak = Cell.class;
                valueOffset = UNSAFE.objectFieldOffset
                    (ak.getDeclaredField("value"));
            } catch (Exception e) {
                throw new Error(e);
            }
        }
    }
  • add方法,getProbe() 是CAS操作获取当前线程的int型变量threadLocalRandomProbe
    public void add(long x) {
        Cell[] as; long b, v; int m; Cell a;
        if ((as = cells) != null || !casBase(b = base, b + x)) {
            boolean uncontended = true;
            if (as == null || (m = as.length - 1) < 0 ||
                (a = as[getProbe() & m]) == null ||
                !(uncontended = a.cas(v = a.value, v + x)))
                longAccumulate(x, null, uncontended);
        }
    }

    final boolean casBase(long cmp, long val) {
        return UNSAFE.compareAndSwapLong(this, BASE, cmp, val);
    }
  • longAccumulate方法:大致流程(1)用threadLocalRandomProbe对数组长度求余确定CAS操作的Cell对象(为null则创建Cell,采用自旋锁(cellsBusy变量)机制保证创建的线程安全);(2)CAS操作失败了则扩容(采用同样的自旋锁机制,容量超过CPU个数则不再扩容),之后重新计算threadLocalRandomProbe(这样就不会重复竞争同一个原子对象),重复步骤(1)
    final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended) {
        int h;
        if ((h = getProbe()) == 0) {
            // (1)初始化当前线程的变量threadLocalRandomProbe
            ThreadLocalRandom.current();
            h = getProbe();
            wasUncontended = true;
        }
        boolean collide = false;                // True if last slot nonempty
        for (;;) {
            Cell[] as; Cell a; int n; long v;
            if ((as = cells) != null && (n = as.length) > 0) { // (2)
                if ((a = as[(n - 1) & h]) == null) { // (3)
                    if (cellsBusy == 0) {       // Try to attach new Cell
                        Cell r = new Cell(x);   // Optimistically create
                        if (cellsBusy == 0 && casCellsBusy()) {
                            boolean created = false;
                            try {               // Recheck under lock
                                Cell[] rs; int m, j;
                                if ((rs = cells) != null &&
                                    (m = rs.length) > 0 &&
                                    rs[j = (m - 1) & h] == null) {
                                    rs[j] = r;
                                    created = true;
                                }
                            } finally {
                                cellsBusy = 0;
                            }
                            if (created)
                                break;
                            continue;           // Slot is now non-empty
                        }
                    }
                    collide = false;
                }
                else if (!wasUncontended)       // CAS already known to fail
                    wasUncontended = true;      // Continue after rehash
                // (4)当前Cell存在,则执行CAS设置
                else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                             fn.applyAsLong(v, x))))
                    break;
                // (5)当前Cell数组元素个数大于CPU个数
                else if (n >= NCPU || cells != as)
                    collide = false;            // At max size or stale
                // (6)是否有冲突
                else if (!collide)
                    collide = true;
                // (7)如果当前元素个数没有达到CPU个数并且有冲突则扩容
                else if (cellsBusy == 0 && casCellsBusy()) {
                    try {
                        if (cells == as) {      // Expand table unless stale
                            // (12.1)
                            Cell[] rs = new Cell[n << 1];
                            for (int i = 0; i < n; ++i)
                                rs[i] = as[i];
                            cells = rs;
                        }
                    } finally {
                        // (7.1)
                        cellsBusy = 0;
                    }
                    // (7.2)
                    collide = false;
                    continue;                   // Retry with expanded table
                }
                // (8)为了能够找到一个空闲的Cell,重新计算hash值,xorshift算法生成随机数
                h = advanceProbe(h);
            }
            // (9)初始化Cell数组
            else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
                boolean init = false;
                try {                           // Initialize table
                    if (cells == as) {
                        // (9.1)
                        Cell[] rs = new Cell[2];
                        // (9.2)
                        rs[h & 1] = new Cell(x);
                        cells = rs;
                        init = true;
                    }
                } finally {
                    // (9.3)
                    cellsBusy = 0;
                }
                if (init)
                    break;
            }
            else if (casBase(v = base, ((fn == null) ? v + x :
                                        fn.applyAsLong(v, x))))
                break;                          // Fall back on using base
        }
    }

其他方法:

  • long sum():返回当前值,即cells数组所有value+base。值不精确,因为循环cells数组时其他线程可能修改了cells数组中的元素或对其扩容
  • void reset():base和cells数组中元素的value置零
  • long sumTheReset():如名字一样。多线程调用时可能有问题,如第一个线程将其清空,第二个线程调用时累加都是0

LongAdder类通过内部cells数组分担了高并发下多线程同时对一个原子变量进行更新时的竞争量,让多个线程可以同时对cells数组里面的元素进行并行的更新操作。另外,数组元素Cell使用@sun.misc.Contended注解修饰,避免了cells数组内多个原子变量被放入同一个缓存行,也就是避免了伪共享

三、LongAccumulator类原理探究

LongAdder的增强版,同样继承Striped64类,可以做自定义的双目计算

    public LongAccumulator(LongBinaryOperator accumulatorFunction,
                           long identity) {
        this.function = accumulatorFunction;
        base = this.identity = identity;
    }

    // 增强版的add方法,可以做其他自定义的双目计算
    public void accumulate(long x) {
        Cell[] as; long b, v, r; int m; Cell a;
        if ((as = cells) != null ||
            (r = function.applyAsLong(b = base, x)) != b && !casBase(b, r)) {
            boolean uncontended = true;
            if (as == null || (m = as.length - 1) < 0 ||
                (a = as[getProbe() & m]) == null ||
                !(uncontended =
                  (r = function.applyAsLong(v = a.value, x)) == v ||
                  a.cas(v, r)))
                longAccumulate(x, function, uncontended);
        }
    }

 

 

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页