JUC学习笔记-----LongAdder

LongAdder

关键域

public class LongAdder extends Striped64 implements Serializable {
    // 1. 累加单元数组(懒加载,竞争激烈时扩容)
    transient volatile Cell[] cells;

    // 2. 基础值(无竞争时直接累加,避免创建 Cell 数组的开销)
    transient volatile long base;

    // 3. cells 初始化/扩容的锁标记(0 = 未加锁;1 = 加锁中)
    transient volatile int cellsBusy;

    // Cell 内部类(分段累加的单元,避免竞争)
    static final class Cell {
        volatile long value; // 每个 Cell 的累加值(用 volatile 保证可见性)
        Cell(long x) { value = x; }
    }
}
1. cells 字段
transient volatile Cell[] cells;

实现分段累加(类似 ConcurrentHashMap 的分段锁思想 ),将竞争分散到不同 Cell,降低锁竞争。

  • 竞争激烈时,cells 数组会扩容(默认 2 倍),每个线程哈希到不同 Cell 累加,避免线程间直接竞争。
  • transient:反序列化时重新初始化,避免持久化 cells 状态(无意义)。
  • volatile:保证 cells 数组的可见性,线程创建 / 扩容时能感知最新数组。
2. base 字段
transient volatile long base;

优化无竞争场景的性能,避免创建 cells 数组的开销。

  • 当线程竞争小(或无竞争)时,直接通过 CAS 累加 base,无需操作 cells
  • transient:反序列化时重新计算 base(依赖 cells 合并结果,无需持久化 )。
  • volatile:保证 base 的可见性,多线程累加 / 读取时无歧义。
3. cellsBusy 字段
transient volatile int cellsBusy;

实现自旋锁,保证 cells 数组初始化 / 扩容时的线程安全。

  • 0:表示 cells 可初始化 / 扩容。
  • 1:表示 cells 正在初始化 / 扩容,其他线程需自旋等待。
  • 类似 ReentrantLock 的非公平锁,通过 CAS 尝试修改 cellsBusy 实现加锁,避免重量级锁开销。
4. Cell 内部类
static final class Cell {
    volatile long value;
    Cell(long x) { value = x; }
}
  • 每个 Cell 是一个独立的累加单元,线程哈希到某个 Cell 后,直接修改 Cell.value,避免线程间竞争。
  • volatile long value:保证 value 的可见性,多线程修改同一 Cell 时(极端情况),仍能通过 CAS 保证原子性。
  • 分段累加的最小单位,cells 数组扩容时,Cell 数量增加,进一步分散竞争。

伪共享

其中Cell即为累加单元

// 防止缓存行伪共享
@sun.misc.Contended
static final class Cell {
    volatile long value;

    Cell(long x) {
        value = x;
    }

    // 最重要的方法,用来 cas 方式进行累加,prev 表示旧值,next 表示新值
    final boolean cas(long prev, long next) {
        return UNSAFE.compareAndSwapLong(this, valueOffset, prev, next);
    }

    // 省略不重要代码
    // 这里需要注意,实际源码中还会有 valueOffset 的静态初始化等,如下(补充完整关键部分)
    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 与内存的速度差异很大,需要靠预读数据至缓存来提升效率。

而缓存以缓存行为单位,每个缓存行对应着一块内存,一般是 64 byte(8 个 long)

缓存的加入会造成数据副本的产生,即同一份数据会缓存在不同核心的缓存行中

CPU 要保证数据的一致性,如果某个 CPU 核心更改了数据,其它 CPU 核心对应的整个缓存行必须失效

  1. Cell 数组连续存储,一个缓存行(64B)可存 2 个 Cell(每个 24B )。
  2. 不同 CPU 核心(Core-0/Core-1 )修改相邻 Cell 时,会因缓存行共享,导致对方缓存行失效,触发频繁缓存同步,降低性能。
  3. @sun.misc.Contended 作用

    • 在注解修饰的对象(如 Cell )前后插入 128B 的 padding(填充),让每个 Cell 独占缓存行。
    • 避免 “修改一个 Cell 导致相邻 Cell 所在缓存行失效” 的问题,提升多线程累加(如 LongAdder )的性能。

add

public void add(long x) {
    Cell[] as; 
    long b, v; 
    int m; 
    Cell a; 

    // 分支 1:无竞争(直接累加 base)或 轻度竞争(尝试 CAS 累加 Cell)
    if ((as = cells) != null || !casBase(b = base, b + x)) {
        boolean uncontended = true; 

        // 分支 2:检查 cells 数组是否可用 + 哈希到具体 Cell
        if (as == null || (m = as.length - 1) < 0 || 
            (a = as[getProbe() & m]) == null || 
            // 核心:尝试 CAS 累加当前 Cell 的 value
            !(uncontended = a.cas(v = a.value, v + x))) { 

            // 冲突升级:调用 longAccumulate 处理(扩容 cells/重试 CAS)
            longAccumulate(x, null, uncontended); 
        }
    }
}
  1. 优先无锁累加
    尝试用 CAS 直接累加 base → 无竞争时性能最优。

  2. 降级分段累加
    累加 base 失败(竞争发生),尝试在 cells 数组中哈希到 Cell,用 CAS 累加 → 分散竞争。

  3. 冲突升级处理
    若 Cell 的 CAS 仍失败(重度竞争 ),调用 longAccumulate 处理:

    • 初始化 cells(首次竞争 )。
    • 扩容 cells 数组(竞争持续 )。
    • 重试 CAS 或切换 Cell → 最终保证累加成功。

longAccumulate

final void longAccumulate(long x, LongBinaryOperator fn, boolean wasUncontended) {
    int h; 
    // 初始化线程哈希值(用于分配 Cell)
    if ((h = getProbe()) == 0) { 
        ThreadLocalRandom.current(); // 强制初始化探针
        h = getProbe(); 
        wasUncontended = true; 
    }
    boolean collide = false; 

    // 自旋重试,直到累加成功
    for (;;) { 
        Cell[] as; Cell a; int n; long v; 

        // 分支 1:cells 数组可用(已初始化)
        if ((as = cells) != null && (n = as.length) > 0) { 
            // 哈希到具体 Cell
            if ((a = as[(n - 1) & h]) == null) { 
                // Cell 为空,尝试加锁初始化 Cell
                if (cellsBusy == 0 && casCellsBusy()) { 
                    try { 
                        if (cells != as) continue; // 检查 cells 是否被修改
                        // 初始化空 Cell
                        as[(n - 1) & h] = new Cell(x); 
                    } finally {
                        cellsBusy = 0; // 解锁
                    }
                    continue; // 重试累加
                }
                collide = false; // 标记未冲突
            } 
            // 之前累加 Cell 失败(wasUncontended = false)
            else if (!wasUncontended) { 
                wasUncontended = true; 
                h = getProbe(); // 重新哈希,换 Cell 重试
            } 
            // 尝试 CAS 累加当前 Cell
            else if (fn == null ? 
                a.cas(v = a.value, v + x) : // 无函数,直接累加
                a.cas(v = a.value, fn.applyAsLong(v, x))) { // 有函数,应用函数
                break; // 累加成功,退出循环
            } 
            // cells 长度小于最大容量(CPU 核心数),允许扩容
            else if (n >= NCPU || !cellsEquals(as, cells)) { 
                collide = false; 
            } 
            // 标记冲突,准备扩容
            else if (!collide) { 
                collide = true; 
            } 
            // 冲突持续,尝试扩容 cells
            else if (cellsBusy == 0 && casCellsBusy()) { 
                try {
                    if (cells == as) { 
                        // 扩容为 2 倍
                        cells = Arrays.copyOf(as, n << 1); 
                    }
                } finally {
                    cellsBusy = 0; // 解锁
                }
                collide = false; 
                continue; // 扩容后重试
            }
            h = advanceProbe(h); // 哈希值自增,换 Cell 重试
        } 

        // 分支 2:cells 未初始化,尝试加锁初始化
        else if (cellsBusy == 0 && cells == as && casCellsBusy()) { 
            try {
                if (cells != as) continue; // 检查 cells 是否被修改
                // 初始化 cells 数组(长度为 2)
                cells = new Cell[2]; 
                // 哈希到第一个 Cell,初始化值
                cells[(n = 1) & h] = new Cell(x); 
            } finally {
                cellsBusy = 0; // 解锁
            }
            continue; // 重试累加
        } 

        // 分支 3:cells 不可用,尝试累加 base
        else if (fn == null ? 
            casBase(v = base, v + x) : // 无函数,直接累加 base
            casBase(v = base, fn.applyAsLong(v, x))) { // 有函数,应用函数
            break; // 累加成功,退出循环
        } 
    }
}

cells数组不存在情况

  1. 检查初始化条件
    确认 cells 未初始化且无其他线程持有 cellsBusy 锁(cellsBusy == 0),同时验证 cells 引用未被修改(cells == as)。

  2. 尝试加锁
    通过 casCellsBusy() 尝试将 cellsBusy 从 0 置为 1,获取初始化 cells 的权限。

  3. 二次校验
    加锁后再次检查 cells 引用是否变化(防止并发修改),若变化则放弃本次初始化,重新进入循环。

  4. 初始化 cells 数组
    创建长度为 2 的 cells 数组,根据线程哈希值(h)计算索引,初始化对应位置的 Cell 为 x(1)

  5. 释放锁
    将 cellsBusy 重置为 0,释放初始化权限。

  6. 重试累加流程
    初始化完成后,通过 continue 重新进入循环,尝试在新创建的 cells 数组中完成累加。

  7. 降级处理(加锁失败时)
    若加锁失败(其他线程正在初始化 cells),则尝试直接累加 base,成功则退出循环,失败则重新进入循环重试。

数组创建好了,但是线程对应的累加单元还没创建情况

  1. 定位目标 Cell:通过线程哈希值与数组长度取模,定位到线程对应的 Cell 位置。
  2. 检查 Cell 状态:发现目标 Cell 为 null(未创建)。
  3. 尝试加锁:检查 cellsBusy 为 0 时,通过 casCellsBusy () 加锁,获取 Cell 创建权限。
  4. 二次校验:加锁后再次确认 cells 数组未被修改,避免并发冲突。
  5. 创建 Cell:在目标位置初始化 Cell 并赋值。
  6. 释放锁:将 cellsBusy 重置为 0,释放权限。
  7. 重试累加:通过 continue 重新进入循环,尝试在新创建的 Cell 上完成累加。
  8. 冲突处理:若加锁失败(其他线程操作),标记无冲突后重新哈希换 Cell 重试。

cells存在&cell已创建情况

  • 定位目标 Cell:通过线程哈希值与 cells 数组长度取模,定位到已创建的 Cell。
  • 首次冲突标记重置:若之前累加失败(wasUncontended 为 false),重置标记并更新线程哈希值换 Cell 重试。
  • 尝试 CAS 累加:对当前 Cell 的 value 执行 CAS 累加操作,成功则退出循环。
  • 判断扩容条件:若 cells 数组长度≥CPU 核心数或数组已被修改,标记无需扩容。
  • 标记冲突状态:首次冲突时标记 collide 为 true,为后续扩容做准备。
  • 执行扩容操作:冲突持续且获取到 cellsBusy 锁时,将 cells 数组扩容为原来的 2 倍,释放锁后重试。
  • 更新哈希重试:通过 advanceProbe 更新线程哈希值,切换到其他 Cell 重新尝试累加。

sum

public long sum() {
    Cell[] as = cells; // 引用当前 cells 数组
    Cell a; 
    long sum = base; // 初始化为 base 的值

    // cells 数组已初始化(存在分段累加)
    if (as != null) { 
        // 遍历所有 Cell
        for (int i = 0; i < as.length; ++i) { 
            // 跳过空 Cell(可能未初始化)
            if ((a = as[i]) != null) { 
                sum += a.value; // 累加 Cell 的值
            }
        }
    }
    return sum; // 返回合并后的总和
}
  1. 初始化总和
    将 sum 初始化为 base 的值 → 包含无竞争场景的累加结果。

  2. 合并 cells 数组
    遍历 cells 数组,累加所有非空 Cell 的值 → 包含分段竞争场景的累加结果。

  3. 返回最终结果
    返回 base + cells 合并后的总和 → 保证最终一致性(可能非实时,但结果正确 )。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值