概述与案例
- 在上一篇中(第1篇:并发基石CAS),我们分析了CAS相关的问题。
- 那么我们就可以发现,CAS实现了很多的功能,但是现在网站的流量很大,我想测试一下在高并发场景下的性能。那么我们就分为几种方式去评估。
- 需求:统计网站访问次数,假设访问100W次,看一下不同线程不同加锁方式使用的时间。代码如下
/**
* 多线程情况下的比较
*
* @author icanci
* @since 1.0 Created in 2022/06/06 21:28
*/
public class MoreThreadDemo {
// 1千万
protected static final int count = 1000 * 10000;
protected static int num = 0;
public static void main(String[] args) throws Exception {
// 线程数量 - 执行多少次( 1000 * 10000 )
tests(1, count);
tests(2, count);
tests(10, count);
tests(20, count);
tests(40, count);
tests(60, count);
tests(80, count);
tests(100, count);
}
/**
* test
*
* @param threadCount 线程数量
* @param times 每个线程自增多少次
*/
private static void tests(final int threadCount, final int times) {
// synchronized
staticLog(() -> {
testSynchronized(threadCount, times);
}, "synchronized");
// AtomicInteger
staticLog(() -> {
testAtomicInteger(threadCount, times);
}, "AtomicInteger");
// AtomicLong
staticLog(() -> {
testAtomicLong(threadCount, times);
}, "AtomicLong");
// LongAdder
staticLog(() -> {
testLongAdder(threadCount, times);
}, "LongAdder");
// LongAccumulator
staticLog(() -> {
testLongAccumulator(threadCount, times);
}, "LongAccumulator");
System.out.println("-------------------- 华丽的分隔线 --------------------");
// 归零
num = 0;
}
/**
* 测试 Synchronized
*/
private static void testSynchronized(int threadCount, int times) {
try {
List<Thread> threads = new ArrayList<>();
CountDownLatch countDownLatch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
threads.add(new Thread(() -> {
try {
for (int j = 0; j < times; j++) {
doSum();
}
} finally {
countDownLatch.countDown();
}
}));
}
for (Thread thread : threads) {
thread.start();
}
countDownLatch.await();
System.out.println("synchronized结果:" + num);
} catch (Exception e) {
//
}
}
private static synchronized void doSum() {
num++;
}
/**
* 测试 AtomicInteger
*/
private static void testAtomicInteger(int threadCount, int times) {
try {
AtomicInteger atomicInteger = new AtomicInteger();
List<Thread> threads = new ArrayList<>();
CountDownLatch countDownLatch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
threads.add(new Thread(() -> {
try {
for (int j = 0; j < times; j++) {
atomicInteger.incrementAndGet();
}
} finally {
countDownLatch.countDown();
}
}));
}
for (Thread thread : threads) {
thread.start();
}
countDownLatch.await();
System.out.println("atomicInteger结果:" + atomicInteger.get());
} catch (Exception e) {
//
}
}
/**
* 测试 AtomicLong
*/
private static void testAtomicLong(final int threadCount, final int times) {
try {
AtomicLong atomicLong = new AtomicLong();
List<Thread> threads = new ArrayList<>();
CountDownLatch countDownLatch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
threads.add(new Thread(() -> {
try {
for (int j = 0; j < times; j++) {
atomicLong.incrementAndGet();
}
} finally {
countDownLatch.countDown();
}
}));
}
for (Thread thread : threads) {
thread.start();
}
countDownLatch.await();
System.out.println("atomicLong结果:" + atomicLong.get());
} catch (Exception e) {
//
}
}
/**
* 测试 LongAdder
*/
private static void testLongAdder(int threadCount, int times) {
try {
LongAdder longAdder = new LongAdder();
List<Thread> threads = new ArrayList<>();
CountDownLatch countDownLatch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
threads.add(new Thread(() -> {
try {
for (int j = 0; j < times; j++) {
longAdder.increment();
}
} finally {
countDownLatch.countDown();
}
}));
}
for (Thread thread : threads) {
thread.start();
}
countDownLatch.await();
System.out.println("longAdder结果:" + longAdder.longValue());
} catch (Exception e) {
//
}
}
/**
* 测试 LongAccumulator
*/
private static void testLongAccumulator(int threadCount, int times) {
try {
LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x + y, 0);
List<Thread> threads = new ArrayList<>();
CountDownLatch countDownLatch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
threads.add(new Thread(() -> {
try {
for (int j = 0; j < times; j++) {
longAccumulator.accumulate(1);
}
} finally {
countDownLatch.countDown();
}
}));
}
for (Thread thread : threads) {
thread.start();
}
countDownLatch.await();
System.out.println("longAccumulator结果:" + longAccumulator.longValue());
} catch (Exception e) {
//
}
}
/**
* log
*/
protected static void staticLog(MyConsumer consumer, String name) {
long start = System.currentTimeMillis();
consumer.apply();
long end = System.currentTimeMillis();
System.out.println(name + " 执行了:" + (end - start) + "ms");
}
}
@FunctionalInterface
interface MyConsumer {
// apply
void apply();
}
- 代码如上,执行结果如下
synchronized结果:10000000
synchronized 执行了:102ms
atomicInteger结果:10000000
AtomicInteger 执行了:74ms
atomicLong结果:10000000
AtomicLong 执行了:75ms
longAdder结果:10000000
LongAdder 执行了:77ms
longAccumulator结果:10000000
LongAccumulator 执行了:75ms
-------------------- 华丽的分隔线 --------------------
synchronized结果:20000000
synchronized 执行了:411ms
atomicInteger结果:20000000
AtomicInteger 执行了:334ms
atomicLong结果:20000000
AtomicLong 执行了:346ms
longAdder结果:20000000
LongAdder 执行了:76ms
longAccumulator结果:20000000
LongAccumulator 执行了:77ms
-------------------- 华丽的分隔线 --------------------
synchronized结果:100000000
synchronized 执行了:636ms
atomicInteger结果:100000000
AtomicInteger 执行了:3630ms
atomicLong结果:100000000
AtomicLong 执行了:3806ms
longAdder结果:100000000
LongAdder 执行了:142ms
longAccumulator结果:100000000
LongAccumulator 执行了:138ms
-------------------- 华丽的分隔线 --------------------
synchronized结果:200000000
synchronized 执行了:1280ms
atomicInteger结果:200000000
AtomicInteger 执行了:7604ms
atomicLong结果:200000000
AtomicLong 执行了:7353ms
longAdder结果:200000000
LongAdder 执行了:256ms
longAccumulator结果:200000000
LongAccumulator 执行了:271ms
-------------------- 华丽的分隔线 --------------------
synchronized结果:400000000
synchronized 执行了:2670ms
atomicInteger结果:400000000
AtomicInteger 执行了:15345ms
atomicLong结果:400000000
AtomicLong 执行了:15071ms
longAdder结果:400000000
LongAdder 执行了:513ms
longAccumulator结果:400000000
LongAccumulator 执行了:544ms
-------------------- 华丽的分隔线 --------------------
synchronized结果:600000000
synchronized 执行了:4210ms
atomicInteger结果:600000000
AtomicInteger 执行了:23090ms
atomicLong结果:600000000
AtomicLong 执行了:22758ms
longAdder结果:600000000
LongAdder 执行了:839ms
longAccumulator结果:600000000
LongAccumulator 执行了:865ms
-------------------- 华丽的分隔线 --------------------
synchronized结果:800000000
synchronized 执行了:5773ms
atomicInteger结果:800000000
AtomicInteger 执行了:28619ms
atomicLong结果:800000000
AtomicLong 执行了:28628ms
longAdder结果:800000000
LongAdder 执行了:1221ms
longAccumulator结果:800000000
LongAccumulator 执行了:1109ms
-------------------- 华丽的分隔线 --------------------
synchronized结果:1000000000
synchronized 执行了:7343ms
atomicInteger结果:1000000000
AtomicInteger 执行了:37131ms
atomicLong结果:1000000000
AtomicLong 执行了:37012ms
longAdder结果:1000000000
LongAdder 执行了:1314ms
longAccumulator结果:1000000000
LongAccumulator 执行了:1353ms
-------------------- 华丽的分隔线 --------------------
- 实现方式-加锁
- 实现方式-AtomicInteger
- 实现方式-AtomicLong
- 实现方式-LongAdder
- 实现方式-LongAccumulator
- 经过对比之后发现,LongAdder性能非常优秀,尤其是在多线程的场景下。并且LongAccumulator的性能也不错,因为LongAccumulator和LongAdder的实现方式是完全一样的,只是做了一些定制。通过
LongBinaryOperator
函数,后面我们在看源码的时候会看到,二者唯一的不同就是计算是用的值还是用的LongBinaryOperator
来计算。LongAccumulator性能慢一些,是因为Lambda表达式(方法引用)会额外的创建一些对象。
LongAdder图解
- LongAdder的结构比较简单,如下图
- 其由base值(long类型)和Cell数组构成,如上图
- 当没有base的更新没有线程竞争的时候,会直接写到base里面去,而不会操作Cell数组,当base的写出现了竞争的时候,就会创建Cell数组,由不同的线程写不同的下标。当最后求和的时候,通过上述的公式,sum = base + 所有槽的值。
- 执行流程图
- 刚开始第一次去理解上述的内容比较抽象,我现在从源码开始讲起。
LongAdder源码
- LongAdder的属性,在其父类Striped64中
abstract class Striped64 extends Number {
/*
* @sun.misc.Contended 添加缓存填充,用来消除伪共享
* 伪共享会导致缓存行失效,缓存一致性开销变大。
* 伪共享指的是多个线程同时读写同一个缓存行的不同变量时导致的 CPU缓存失效。
* 尽管这些变量之间没有任何关系,但由于在主内存中邻近,存在于同一个缓存行之中,
* 它们的相互覆盖会导致频繁的缓存未命中,引发性能下降。这里对于伪共享我只是提一下概念,并不会深入去讲解,大家可以自行查阅一些资料。
* 解决伪共享的方法一般都是使用直接填充,我们只需要保证不同线程的变量存在于不同的 CacheLine 即可,
* 使用多余的字节来填充可以做点这一点,这样就不会出现伪共享问题。在Disruptor队列的设计中就有类似设计。
*/
@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);
}
}
}
/** Number of CPUS, to place bound on table size */
// CPU核心数
static final int NCPU = Runtime.getRuntime().availableProcessors();
/**
* Table of cells. When non-null, size is a power of 2.
* Cells 数组 是2的幂
*/
transient volatile Cell[] cells;
/**
* Base value, used mainly when there is no contention, but also as
* a fallback during table initialization races. Updated via CAS.
* base 基础value 值,当并发较低的时候,只累加该值,主要用于没有竞争的情况,通过CAS更新
*/
transient volatile long base;
/**
* Spinlock (locked via CAS) used when resizing and/or creating Cells.
* 创建或者落入Cells数组的时候使用的自旋锁变量调整单元格大小,创建单元格时候使用的锁
* 初始化cells或者扩容cells需要获取锁 0表示无锁状态 1表示其他线程持有了锁
*/
transient volatile int cellsBusy;
/** Number of CPUS, to place bound on table size */
// CPU核心数
static final int NCPU = Runtime.getRuntime().availableProcessors();
/**
* Table of cells. When non-null, size is a power of 2.
* Cells 数组 是2的幂
*/
transient volatile Cell[] cells;
/**
* Base value, used mainly when there is no contention, but also as
* a fallback during table initialization races. Updated via CAS.
* base 基础value 值,当并发较低的时候,只累加该值,主要用于没有竞争的情况,通过CAS更新
*/
transient volatile long base;
/**
* Spinlock (locked via CAS) used when resizing and/or creating Cells.
* 创建或者落入Cells数组的时候使用的自旋锁变量调整单元格大小,创建单元格时候使用的锁
* 初始化cells或者扩容cells需要获取锁 0表示无锁状态 1表示其他线程持有了锁
*/
transient volatile int cellsBusy;
}
- LongAdder#add()方法
- 我们通常使用的是
LongAdder的increment()
方法,也就是自增方法,这里LongAdder其实调用的是add()
方法
public class LongAdder extends Striped64 implements Serializable {
/**
* Equivalent to {@code add(1)}.
* 自增 实际上调用的是 add(1L);
*/
public void increment() {
add(1L);
}
/**
* Equivalent to {@code add(-1)}.
* 自减 实际上调用的是 add(1L);
*/
public void decrement() {
add(-1L);
}
}
- LongAdder继承Striped64,并且使用到了Striped64中的方法,这是一个公共方法,我们稍后再说。
- 现在来分析LongAdder的add方法
public class LongAdder extends Striped64 implements Serializable {
/**
* Adds the given value.
*
* 1.casBase
* 2.casBase失败,创建cells
* 3.cells失败,扩容cells,扩容到CPU核心数大小
*
* @param x the value to add
*/
public void add(long x) {
// as 表示当前cells引用
// b 表示获取的base值
// v 表示期望值
// m 表示cells数组的长度
// a 表示当前线程命中的cell单元格
Cell[] as; long b, v; int m; Cell a;
// 条件1:true 表示cell已经初始化过了 当前线程应该将数据写入到对应的cell中
// false 表示第一次加载,所有的数据应该尝试写到base中
// 条件2:true 表示当前线程CAS数据替换失败 这里指的是 !casBase(x,y) 整体是否为true
// false 表示当前线程CAS成功
if ((as = cells) != null || !casBase(b = base, b + x)) {
// 进入到方法的前提条件
// case1: cells已经初始化,需要尝试将数据写入到对应的cell,注意,这里是尝试写入
// case2: false 发生竞争了,可能需要重试或者扩容
// ture 未竞争 false 竞争
boolean uncontended = true;
// 第一次 as 为null
// 已经新建了数组了
// 如果某个calls槽cas失败,就可能进行扩容操作
// 如果第一次写发现值为null,初始化了cell,但是没有设置值。需要设置值
// 如果不为null,就进行cas操作
// 如果不为null,就进行cas操作 且 cas操作失败,然后扩容
// 条件1:as == null 说明当前cells未初始化
// 条件2:(m = as.length - 1) < 0 cells数组长度等于0 m = cells数组长度,且长度一定为2的幂,后续会看到或参考HashMap的扩容
// 条件3:(a = as[getProbe() & m]) == null 当前线程的cells[index] 对应的数据为null,说明是第一次写值
// 捞一下getProbe():获取当前线程的hash值与上cells长度-1,计算cells中的下标
// 条件4:!(uncontended = a.cas(v = a.value, v + x)) 当前 a = cells[index] cas写失败 此时 uncontended = false 发生竞争了
// 满足上述条件都会执行累加
if (as == null || (m = as.length - 1) < 0 ||
(a = as[getProbe() & m]) == null ||
!(uncontended = a.cas(v = a.value, v + x)))
// 执行累加 调用父类Striped64的longAccumulate方法
longAccumulate(x, null, uncontended);
}
}
}
Striped64中一些操作方法
- Striped64提供了数据的CAS操作,和线程的hash处理,如下
abstract class Striped64 extends Number {
/**
* CASes the base field.
*/
final boolean casBase(long cmp, long val) {
// cas 进行更新 base值
// 返回是否更新成功,CAS只写一次
// 可能写失败,写失败就开始建立Cells
return UNSAFE.compareAndSwapLong(this, BASE, cmp, val);
}
/**
* CASes the cellsBusy field from 0 to 1 to acquire lock.
* cas 操作cellsBusy 的值
*/
final boolean casCellsBusy() {
return UNSAFE.compareAndSwapInt(this, CELLSBUSY, 0, 1);
}
/**
* Returns the probe value for the current thread.
* Duplicated from ThreadLocalRandom because of packaging restrictions.
* 得到线程的hash值
*/
static final int getProbe() {
return UNSAFE.getInt(Thread.currentThread(), PROBE);
}
/**
* Pseudo-randomly advances and records the given probe value for the
* given thread.
* Duplicated from ThreadLocalRandom because of packaging restrictions.
* 重置当前线程的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;
}
}
LongAdder父类Striped64的longAccumulate方法
- 在这个方法中,我们就提到了之前的
LongBinaryOperator
函数,实际在进行计算的时候,是判断是否有这个值来进行处理的。
abstract class Striped64 extends Number {
/**
* Handles cases of updates involving initialization, resizing,
* creating new Cells, and/or contention. See above for
* explanation. This method suffers the usual non-modularity
* problems of optimistic retry code, relying on rechecked sets of
* reads.
*
* @param x the value 期望的值
* @param fn the update function, or null for add (this convention
* avoids the need for an extra field or function in LongAdder).
* 执行更新的方法
* @param wasUncontended false if CAS failed before call // Cells 初始化之后,并当前线程CAS修改失败 为false
*
* 如何进来这个方法
* 1.cells未初始化并且casBase失败
* 2.cells初始化并且当前线程cas失败
* 3.cells的对应的值为null,第一次写值
*/
final void longAccumulate(long x, LongBinaryOperator fn,
boolean wasUncontended) {
// h: 线程hash值
int h;
// 还没有给线程分配hash值,这个时候先分配一手hash值
if ((h = getProbe()) == 0) {
// 等于0 强制设置为当前线程,再去获取 hash 值
ThreadLocalRandom.current(); // force initialization 强制
// 重新获取 probe 值 ,hash值被设置为一个全新的线程,所以设置了 wasUncontended 为true
h = getProbe();
// 重新计算了,认为不是热点 也即是说,线程第一次进来,计算的hash值为0,当前线程的hash值0与任何数据位运算都是0,
// 那么就会落到第一个cell,这是不合理的,所以需要将wasUncontended设置为true
// 也就是说,首次线程访问,先会写cell[0]号位置,写成功了,就没有下文了,写失败了,说明发生了竞争,那么就不适合再给线程设置为0了,因为已经发生了抢夺
wasUncontended = true;
}
// collide 表示扩容意向,false 一定不会扩容,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;
// CASE1:cells 已经初始化了 则操作 cells
if ((as = cells) != null && (n = as.length) > 0) {
// CASE1.1: 如果计算完成之后,计算当前的cell单元为null,说明这个cell没有被使用
// 那么就需要新建cell
if ((a = as[(n - 1) & h]) == null) {
// cellsBusy == 0 锁没有被占用
// 如果cells数组没有再扩容
if (cellsBusy == 0) { // Try to attach new Cell
// 创建一个cell单元
Cell r = new Cell(x); // Optimistically create
// 尝试加锁
if (cellsBusy == 0 && casCellsBusy()) {
boolean created = false;
// 在有锁的情况下在检查一下之前的判断
try { // Recheck under lock
// 创建并且使用
Cell[] rs; int m, j;
// 条件1和条件2恒成立,因为进来的时候就已经判断了
// 这里大家可能会有疑惑,为毛没有再判断一次 cells == as
// 因为这个时候,两个线程A、B进来之后,A停在加锁之前,B进来了,那么A、B必然插入的是同一个槽
// 那么这个时候会判断 rs[j = (m - 1) & h] == null) 如果为null才赋值,否则就不管了
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;
// 这个时候目标槽已经不是空了 继续自旋,这个时候这个地方就进不来了
// LongAdder 必须保证每个值都写进去
continue; // Slot is now non-empty
}
}
// 扩容意向修改为false
// 因为当前线程还没写值,不一定写失败,所以扩容意向为false
collide = false;
}
// CASE1.2:只有一种情况会进来,cells初始化之后,当前线程竞争修改失败,并且已经初始化线程hash值
// wasUncontended = false 这里是重新设置这个值为 true
// 紧接着执行 advanceProbe(h) 重置当前线程的hash,继续循环
else if (!wasUncontended) // CAS already known to fail
wasUncontended = true; // Continue after rehash
// 如果为true。就继续执行当前cell位的cas操作
// CASE1.3:当前线程rehash过hash值,新命中的cell不为空,就来到这里,然后进行cas写
// 如果写成功了,跳出循环
// 如果写失败了,表示rehash之后,又写失败了,进入下一个条件
else if (a.cas(v = a.value, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break;
// CASE1.4:
// 条件1:n 是数组长度 大于CPU核心数,collide 设置为false,扩容意向不搞了,
// 条件2:cells != as 其他线程已经扩容过了,当前线程重新hash
else if (n >= NCPU || cells != as)
// 如果n大于等于 NCPU 不可扩容
collide = false; // At max size or stale
// CASE1.5:
// 设置扩容意向为true,但是不一定扩容成功
// 如果扩容意向为false,重新设置为true 然后继续执行循环
// 设置为true之后,再次rehash然后再次循环,如果又失败了,这里且不满足 (n >= NCPU || cells != as)
// 此时collide为true 执行 CASE1.6
else if (!collide)
collide = true;
// CASE1.6:扩容操作并加锁 注意:只是扩容,并没有进行赋值
// 条件1:cellsBusy == 0 表示当前没有占用锁,当前线程可以抢锁
// 条件2: casCellsBusy() 抢锁
else if (cellsBusy == 0 && casCellsBusy()) {
try {
// 和上面一样,需要判断
if (cells == as) { // Expand table unless stale
// 扩容2倍
Cell[] rs = new Cell[n << 1];
for (int i = 0; i < n; ++i)
rs[i] = as[i];
// 扩容赋值
cells = rs;
}
} finally {
// 释放锁
cellsBusy = 0;
}
// 扩容意向设置为false
collide = false;
continue; // Retry with expanded table
}
// 重新设置线程hash值 h
h = advanceProbe(h);
}
// CASE2: cells 没加锁并且没有初始化(cells为null),则尝试进行加锁并且开始初始化操作
// 条件1:cellsBusy == 0 没有加锁
// 条件2:cells == as 为什么又对比一次?因为多线程场景下,A线程过来,发现是null,然后进入了,然后B线程过来,不一定是null
// 可能其他线程会修改cells
// 条件3:表示cas获取锁成功,将cellsBusy设置为1,失败表示其他线程正在持有锁
else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
// 初始化操作
boolean init = false;
try { // Initialize table
// 初始化的时候,进行创建cells数组
// casBase 失败,新建大小为2的cells数组
// 为毛又对比一下?因为是这样的,有可能在执行 cellsBusy == 0 && cells == as && casCellsBusy() 的时候
// 执行到 cellsBusy == 0 && cells == as 线程让出CPU 然后另外一个线程执行为true,那么其可以继续操作cells
// 此时在切换过来,这个cells就不是null了
// 类似双重检查锁 - 这里是安全的,因为多线程场景下只有一个线程能够拿到锁,使用casCellsBusy()保证
if (cells == as) {
// 新建大小为2的cells数组
Cell[] rs = new Cell[2];
// 设置值
rs[h & 1] = new Cell(x);
// 赋值cells
cells = rs;
// 初始化成功
init = true;
}
} finally {
cellsBusy = 0;
}
if (init)
break;
}
// CASE3:cells 正在初始化 则尝试在base上进行累加
// 1.当前cellsBusy被别的线程持有,这个时候需要进行兜底操作,不能让灵魂无处安放
// 2.cells被其他线程初始化,需要累加到base
else if (casBase(v = base, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break; // Fall back on using base
}
}
}
LongAdder其他方法
- 当没有base的更新没有线程竞争的时候,会直接写到base里面去,而不会操作Cell数组,当base的写出现了竞争的时候,就会创建Cell数组,由不同的线程写不同的下标。当最后求和的时候,通过上述的公式,sum = base + 所有槽的值。
public class LongAdder extends Striped64 implements Serializable {
// 求和方法,只保证最终一致性
// 计算方式如上
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;
}
// 数值归零方法
public void reset() {
Cell[] as = cells; Cell a;
base = 0L;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
a.value = 0L;
}
}
}
// 数值归零并返回sum值
public long sumThenReset() {
Cell[] as = cells; Cell a;
long sum = base;
base = 0L;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null) {
sum += a.value;
a.value = 0L;
}
}
}
return sum;
}
}
总结
- 基本上LongAdder就是这样,比较简单,核心在于父类的longAccumulate方法
- LongAdder的思想很重要,并且对并发的处理也很重要。后续在ConcurrentHashMap中还会遇到类似的逻辑和设计。