longAdder

72 阅读11分钟

longAdder

表示一个值 而已

LongAdder1.png

Striped64

package java.util.concurrent.atomic;
​
import java.util.function.LongBinaryOperator;
import java.util.function.DoubleBinaryOperator;
import java.util.concurrent.ThreadLocalRandom;
​
@SuppressWarnings("serial")
abstract class Striped64 extends Number {
​
    @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);
        }
​
        // Unsafe mechanics
        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);
            }
        }
    }
​
    // 表示当前计算机CPU数量,什么用? 控制cells数组长度的一个关键条件
    static final int NCPU = Runtime.getRuntime().availableProcessors();
    
    transient volatile Cell[] cells;
​
    // 没有发生过竞争时,数据会累加到base上|当cells扩容时,需要将数据写到base中
    transient volatile long base;
​
    // 初始化cells或者扩容cells都需要获取锁,0表示无锁状态,1表示其他线程已经持有锁了
    transient volatile int cellsBusy;
​
    Striped64() {
    }
​
    final boolean casBase(long cmp, long val) {
        return UNSAFE.compareAndSwapLong(this, BASE, cmp, val);
    }
​
    // 通过 CAS 获取锁
    final boolean casCellsBusy() {
        return UNSAFE.compareAndSwapInt(this, CELLSBUSY, 0, 1);
    }
​
    // 获取当前线程的 hash 值
    static final int getProbe() {
        return UNSAFE.getInt(Thread.currentThread(), PROBE);
    }
​
    // 重置当前线程的 hash 值
    static final int advanceProbe(int probe) {
        probe ^= probe << 13;   // xorshift
        probe ^= probe >>> 17;
        probe ^= probe << 5;
        UNSAFE.putInt(Thread.currentThread(), PROBE, probe);
        return probe;
    }
​
    // 都有哪些情况会调用?
    // 1.true->说明cells未初始化,也就是多线程写base发生竞争了[重试|初始化cells]
    // 2.true->说明当前线程对应下标的cell为空,香要创建longAccumulate 支持
    // 3.true->表示cas失败,意味着当前线程对应的cell有竞争[重试|扩容]
    
    // wasUncontended:只有cells初始化之后,并且当前线程竞争修改失败,才会是false
    final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended) {
        int h;
        if ((h = getProbe()) == 0) {
            ThreadLocalRandom.current(); // force initialization
            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) {
                if ((a = as[(n - 1) & h]) == null) {
                    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
                else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                             fn.applyAsLong(v, x))))
                    break;
                else if (n >= NCPU || cells != as)
                    collide = false;            // At max size or stale
                else if (!collide)
                    collide = true;
                else if (cellsBusy == 0 && casCellsBusy()) {
                    try {
                        if (cells == as) {      // Expand table unless stale
                            Cell[] rs = new Cell[n << 1];
                            for (int i = 0; i < n; ++i)
                                rs[i] = as[i];
                            cells = rs;
                        }
                    } finally {
                        cellsBusy = 0;
                    }
                    collide = false;
                    continue;                   // Retry with expanded table
                }
                h = advanceProbe(h);
            }
            else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
                boolean init = false;
                try {                           // Initialize table
                    if (cells == as) {
                        Cell[] rs = new Cell[2];
                        rs[h & 1] = new Cell(x);
                        cells = rs;
                        init = true;
                    }
                } finally {
                    cellsBusy = 0;
                }
                if (init)
                    break;
            }
            else if (casBase(v = base, ((fn == null) ? v + x :
                                        fn.applyAsLong(v, x))))
                break;                          // Fall back on using base
        }
    }
​
    final void doubleAccumulate(double x, DoubleBinaryOperator fn,
                                boolean wasUncontended) {
        int h;
        if ((h = getProbe()) == 0) {
            ThreadLocalRandom.current(); // force initialization
            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) {
                if ((a = as[(n - 1) & h]) == null) {
                    if (cellsBusy == 0) {       // Try to attach new Cell
                        Cell r = new Cell(Double.doubleToRawLongBits(x));
                        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
                else if (a.cas(v = a.value,
                               ((fn == null) ?
                                Double.doubleToRawLongBits
                                (Double.longBitsToDouble(v) + x) :
                                Double.doubleToRawLongBits
                                (fn.applyAsDouble
                                 (Double.longBitsToDouble(v), x)))))
                    break;
                else if (n >= NCPU || cells != as)
                    collide = false;            // At max size or stale
                else if (!collide)
                    collide = true;
                else if (cellsBusy == 0 && casCellsBusy()) {
                    try {
                        if (cells == as) {      // Expand table unless stale
                            Cell[] rs = new Cell[n << 1];
                            for (int i = 0; i < n; ++i)
                                rs[i] = as[i];
                            cells = rs;
                        }
                    } finally {
                        cellsBusy = 0;
                    }
                    collide = false;
                    continue;                   // Retry with expanded table
                }
                h = advanceProbe(h);
            }
            else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
                boolean init = false;
                try {                           // Initialize table
                    if (cells == as) {
                        Cell[] rs = new Cell[2];
                        rs[h & 1] = new Cell(Double.doubleToRawLongBits(x));
                        cells = rs;
                        init = true;
                    }
                } finally {
                    cellsBusy = 0;
                }
                if (init)
                    break;
            }
            else if (casBase(v = base,
                             ((fn == null) ?
                              Double.doubleToRawLongBits
                              (Double.longBitsToDouble(v) + x) :
                              Double.doubleToRawLongBits
                              (fn.applyAsDouble
                               (Double.longBitsToDouble(v), x)))))
                break;                          // Fall back on using base
        }
    }
​
    // Unsafe mechanics
    private static final sun.misc.Unsafe UNSAFE;
    private static final long BASE;
    private static final long CELLSBUSY;
    private static final long PROBE;
    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> sk = Striped64.class;
            BASE = UNSAFE.objectFieldOffset
                (sk.getDeclaredField("base"));
            CELLSBUSY = UNSAFE.objectFieldOffset
                (sk.getDeclaredField("cellsBusy"));
            Class<?> tk = Thread.class;
            PROBE = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomProbe"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }
​
}
//表示当前计算机CPU数量,什么用?控制cells数组长度的一个关键条件
static final int NCPU = Runtime.getRuntime().availableProcessors();
​
// 没有发生过竞争时,数据会累加到 base上 |当cells扩容时,需要将数据写到base中
transient volatile long base;
​
// 初始化cells或者扩容cells都需要栋取锁、0表示无锁状态,1表示其他线程已经持有锁了
transient volatile int cellsBusy;

add方法

public void add(long x) {
    // as 表示cells引用
    // b  表示获取的base值
    // v  表示期望值
    // m  表示 cells数组的长度
    // a  表示当前线程命中的cell单元格
    Cell[] as; long b, v; int m; Cell a;
    // 条件一: true->表示cells已经初始化过了,当前线程应该将数据写入到对应的cell中
    //        false->表示cells未初始化,当前所有线程应该将数据写到base中
    // 条件二: false->表示当前线程cas替换数据成功,
    //        true ->表示发生竞争了,可能需要重试或者扩容
    if ((as = cells) != null || !casBase(b = base, b + x)) {
        // 什么时候进来
        // 1. true->表示 cells 已经初始化过了,当前线程应该将数据写入到对应的cell中   
        // 2. true->表示发生竞争了,可能需要重试或者扩容
        
        // true -> 未竞争  false-> 发生竞争
        boolean uncontended = true;
        // 多线程的情况会导致 出现空 地址一样
        // 条件1:true ->说明 cells 未初始化,也就是多线程写 base 发生竞争了
        //       false ->说明 cells 已经初始化了,当前线程应该是找自己的 cell 写值
        // 
        // 条件2:getProbe()获取当前线程的hash值 m表示cells长度-1 cells长度一定是2的次方数―15= b1111
        //       true->说明当前线程对应下标的cell为空,需要创建longAccumulate支持
        //       false->说明当前线程对应的cell不为空,说明下一步想要将x值添加到cell中。
        // 
        // 条件三:true->表示cas失败,意味着当前线程对应的cell有竞争  
        //       false->表示cas成功
        if (as == null || (m = as.length - 1) < 0 ||
            (a = as[getProbe() & m]) == null ||
            !(uncontended = a.cas(v = a.value, v + x)))
            //都有哪些情况会调用?
            // 1.true -> 说明cells未初始化,也就是多线程写base发生竞争了[重试|初始化cells]
            // 2.true -> 说明当前线程对应下标的cell为空,香要创建longAccumulate 支持
            // 3.true -> 表示cas失败,意味着当前线程对应的cell有竞争[重试|扩容]
            // 调用Striped64中的方法处理
            longAccumulate(x, null, uncontended);
    }
}
​

longAccumulate方法

// 不出意外到 CASE 2 
// 判断有没有锁 没拿到锁 或者其他线程初始化掉了  把值加到base BASE 3
// 1.true -> 说明cells未初始化,也就是多线程写base发生竞争了[重试|初始化cells]
// CASE 1  再到 1.1 创建 CEll(X) 成功的话 拿到锁 其他线程没有设置过 XX
// 2.true -> 说明当前线程对应下标的cell为空,香要创建longAccumulate 支持
// 先 CASE 1.2 修改发生线程的 hash值 CASE 1.3 成功退出 没成功 设置扩容意向 改hash 到 1.5  第二次扩容失败 到 1.6 扩容
// 3.true -> 表示cas失败,意味着当前线程对应的cell有竞争[重试|扩容]
final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended) {
    // 存储线程的probe值 可以理解为 hash 值
    int h;
    // 如果getProbe()方法返回0,说明随机数未初始化
    if ((h = getProbe()) == 0) {
        // 强制初始化
        ThreadLocalRandom.current(); // force initialization
        // 重新获取probe值
        h = getProbe();
        // 都未初始化,肯定还不存在竞争激烈  
        // 为什么?因为默认情况下当前线程肯定是写瓜到了 cells[0]位置。不把它当做一次真正的竞争
        wasUncontended = true;
    }
    // 是否发生碰撞
    boolean collide = false;                // True if last slot nonempty
    // 自旋
    for (;;) {
        // as 表示 cells 引用
        // a  表示当前线程命中的 cell
        // n  表示 cells 数组长度
        // v  表示期望值
        Cell[] as; Cell a; int n; long v;
​
        // cells已经初始化过
        // 初始化后 某一个未空
        // CASE1:表示cells已经初始化了,当前线程应该将数据写入到对应的cell中
        if ((as = cells) != null && (n = as.length) > 0) {
            // 当前线程所在的Cell未初始化
            // 2.true->说明当前线程对应下标的cell为空,需要创建longAccumulate 支持
            // 3.true->表示cas失败,意味着当前线程对应的cell有竞争[重试|扩容]
            
            // CASE 1.1 true -> 表示当前线程对应的下标位置的 cell 为 null ,需要创建new Cell
            if ((a = as[(n - 1) & h]) == null) {
                // true->表示当前锁未被占用   false->表示锁被占用
                // 当前无其它线程在创建或扩容cells,也没有线程在创建Cell
                if (cellsBusy == 0) {       // Try to attach new Cell
                    // 拿当前的x创建Cell
                    // 新建一个Cell,值为当前需要增加的值
                    Cell r = new Cell(x);   // Optimistically create
                    // 条件一: true->表示当前锁未被占用false->表示锁被占用
                    // 条件二:true->表示当前线程获取锁成功false->当前线程获取锁失败..
                    // 再次检测cellsBusy,并尝试更新它为1
                    // 相当于当前线程加锁
                    if (cellsBusy == 0 && casCellsBusy()) {
                        // 是否创建成功标记
                        // 是否创建成功
                        boolean created = false;
                        try {               // Recheck under lock
                            // rs 当前 cells 引用
                            // m cells 长度
                            // j 当前线程命中的下标
                            Cell[] rs; int m, j;
                            // 多线程
                            // 条件一 条件二恒成立
                            // rs[j = (m - 1) & h] == null为了防止其它线程初始化过该位置,然后当前线程再次初始化该位置
                            // 导致丢失数据
                            
                            // 重新获取cells,并找到当前线程hash到cells数组中的位置
                            // 这里一定要重新获取cells,因为as并不在锁定范围内
                            // 有可能已经扩容了,这里要重新获取
                            if ((rs = cells) != null &&
                                (m = rs.length) > 0 &&
                                rs[j = (m - 1) & h] == null) {
                                // 把上面新建的Cell放在cells的j位置处
                                rs[j] = r;
                                // 创建成功
                                created = true; //===
                            }
                        } finally {
                            // 相当于释放锁
                            cellsBusy = 0;
                        }
                        // 创建成功了就返回
                        // 值已经放在新建的Cell里面了
                        if (created)
                            break;
                        continue;           // Slot is now non-empty
                    }
                }
                // 扩容意向强制改为了false
                // 标记当前未出现冲突
                collide = false;
            }
            // 当前线程所在的Cell不为空,且更新失败了
            // 这里简单地设为true,相当于简单地自旋一次
            // 通过下面的语句修改线程的probe再重新尝试
            // CASE1.2:
            // wasUncontended:只有cells初始化之后,并且当前线程竞争修改失败,才会是false
            else if (!wasUncontended)       // CAS already known to fail
                wasUncontended = true;      // Continue after rehash
            // CASE1.3 
            // true  ->写成功,退出循环  到 LongAdder
            // false ->表示rehash之后命中的新的cell 也有竞争 重试1次  再重试 1 次 到 CASE 1.6
            // 再次尝试CAS更新当前线程所在Cell的值,如果成功了就返回
            else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                         fn.applyAsLong(v, x))))
                break;
            // CASE 1.4:
            // 条件一: n >= NCPU true->扩容意向改为false,表示不扩容了 false->说明cells数组还可以扩容
            // 条件二: cells != as true->其它线程已经扩容过了,当前线程rehash之后重试即可
            // CASE 1.5:
            // !collide = true设置扩容意向为true但是不一定真的发生扩容
            // 数组不能超过数组的数量
            
            // 如果cells数组的长度达到了CPU核心数,或者cells扩容了
            // 设置collide为false并通过下面的语句修改线程的probe再重新尝试
            else if (n >= NCPU || cells != as)
                // 扩容意向改为false,表示不扩容了
                collide = false;            // At max size or stale
            
            else if (!collide)
                collide = true;
            // CASE 1.6 正真扩容的
            // 条件一: cellsBusy == 0 true->表示当前无锁状态,当前线程可以去竞争这把锁
            // 条件二: cascellsBusy true->表示当前线程获取锁成功,可以执行扩容逻辑
            // false->表示当前时刻有其它线程在做扩容相关的操作。|
            // casCellsBusy 竞争这把锁
            // 上上个elseif都更新失败了,且上个条件不成立,说明出现冲突了
            // 明确出现冲突了,尝试占有锁,并扩容
            else if (cellsBusy == 0 && casCellsBusy()) {
                try {
                    // 线程 竞争 防止 重复扩容
                    // cells == as
                    // 检查是否有其它线程已经扩容过了
                    if (cells == as) {      // Expand table unless stale
                        // 新数组为原数组的两倍
                        Cell[] rs = new Cell[n << 1];
                        // 把旧数组元素拷贝到新数组中
                        for (int i = 0; i < n; ++i)
                            rs[i] = as[i];
                        // 重新赋值cells为新数组
                        cells = rs;
                    }
                } finally {
                    // 释放锁
                    cellsBusy = 0;
                }
                // 已解决冲突
                collide = false;
                // 使用扩容后的新数组重新尝试
                continue;                   // Retry with expanded table
            }
            // 更新失败或者达到了CPU核心数,重新生成probe,并重试
            h = advanceProbe(h); // 重置 当前线程 hash 值
        }
        // 未初始化过cells数组,尝试占有锁并初始化cells数组
        
        //CASE2:前置条件cells还未初始化as 为null
        //条件一: true 表示当前未加锁
        //条件二: cells == as?因为其它线程可能会在你给as赋值之后修改了cells
        //条件三:true表示获取锁成功会把cellsBusy = 1, false 表示其它线程正在持有这把锁
​
        else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
            // 是否初始化成功
            boolean init = false;
            try {                           // Initialize table
                // 检测是否有其它线程初始化过
                // cells == as?防止其它线程已经初始化了,当前线程再次初始化导致丢失数据
                if (cells == as) {
                    // 新建一个大小为2的Cell数组
                    Cell[] rs = new Cell[2];
                    // 找到当前线程hash到数组中的位置并创建其对应的Cell
                    rs[h & 1] = new Cell(x);
                    // 赋值给cells数组
                    cells = rs;
                    // 初始化成功
                    init = true;
                }
            } finally {
                // 释放锁
                cellsBusy = 0;
            }
            // 初始化成功直接返回
            // 因为增加的值已经同时创建到Cell中了
            if (init)
                break;
        }
        // 如果有其它线程在初始化cells数组中,就尝试更新base
        // 如果成功了就返回
        // CASE3:
        // 1.当前 cellsBusy 加锁状态,表示其它线程正在初始化 cells,所以当前线程将值累加到basel
        // 2.cells 被其它线程初始化后,当前线程需要将数据累加到 base
        else if (casBase(v = base, ((fn == null) ? v + x :
                                    fn.applyAsLong(v, x))))
            break;                          // Fall back on using base
    }
}

sum方法

// sum 方法不是准确的 高精度要用 atomicLong
public long sum() {
    Cell[] as = cells; Cell a;
    long sum = base;
    if (as != null) {
        for (int i = 0; i < as.length; ++i) {
            if ((a = as[i]) != null)
                sum += a.value;
        }
    }
    return sum;
}

参考

死磕 java并发包之LongAdder源码分析 - 彤哥读源码 - 博客园 (cnblogs.com))

LongAdder源码深度讲解