阿里为什么推崇java_为什么阿里巴巴 Java 开发手册推荐使用 LongAdder,而不是 volatile?...

阿里《Java开发手册》最新嵩山版在 8.3 日发布,其中有一段内容引起了老王的注意,内容如下:

【参考】volatile 解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题。

说明:如果是 count++ 操作,使用如下类实现:AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 如果是 JDK8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观 锁的重试次数)。

以上内容共有两个重点:

类似于 count++ 这种非一写多读的场景不能使用

volatile;

如果是 JDK8 推荐使用

LongAdder 而非

AtomicLong 来替代

volatile,因为

LongAdder 的性能更好。

但口说无凭,即使是孤尽大佬说的,咱们也得证实一下,因为马老爷子说过:实践是检验真理的唯一标准。

这样做也有它的好处,第一,加深了我们对知识的认知;第二,文档上只写了LongAdder 比 AtomicLong 的性能高,但是高多少呢?文中并没有说,那只能我们自己动手去测试喽。

话不多,接下来我们直接进入本文正式内容...

volatile 线程安全测试

首先我们来测试 volatile 在多写环境下的线程安全情况,测试代码如下:

public class VolatileExample{

public static volatile int count = 0; // 计数器

public static final int size = 100000; // 循环测试次数

public static void main(String[] args){

// ++ 方式 10w 次

Thread thread = new Thread(() -> {

for (int i = 1; i <= size; i++) {

count++;

}

});

thread.start();

// -- 10w 次

for (int i = 1; i <= size; i++) {

count--;

}

// 等所有线程执行完成

while (thread.isAlive()) {}

System.out.println(count); // 打印结果

}

}

我们把 volatile 修饰的 count 变量 ++ 10w 次,在启动另一个线程 -- 10w 次,正常来说结果应该是 0,但是我们执行的结果却为:

1063

结论:由以上结果可以看出 volatile 在多写环境下是非线程安全的,测试结果和《Java开发手册》相吻合。

LongAdder VS AtomicLong

import org.openjdk.jmh.annotations.*;

import org.openjdk.jmh.infra.Blackhole;

import org.openjdk.jmh.runner.Runner;

import org.openjdk.jmh.runner.RunnerException;

import org.openjdk.jmh.runner.options.Options;

import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.concurrent.TimeUnit;

import java.util.concurrent.atomic.AtomicInteger;

import java.util.concurrent.atomic.LongAdder;

@BenchmarkMode(Mode.AverageTime) // 测试完成时间

@OutputTimeUnit(TimeUnit.NANOSECONDS)

@Warmup(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS) // 预热 1 轮,每次 1s

@Measurement(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 3s

@Fork(1) // fork 1 个线程

@State(Scope.Benchmark)

@Threads(1000) // 开启 1000 个并发线程

public class AlibabaAtomicTest{

public static void main(String[] args) throws RunnerException{

// 启动基准测试

Options opt = new OptionsBuilder()

.include(AlibabaAtomicTest.class.getSimpleName()) // 要导入的测试类

.build();

new Runner(opt).run(); // 执行测试

}

@Benchmark

public int atomicTest(Blackhole blackhole) throws InterruptedException{

AtomicInteger atomicInteger = new AtomicInteger();

for (int i = 0; i 

atomicInteger.addAndGet(1);

}

// 为了避免 JIT 忽略未被使用的结果

return atomicInteger.intValue();

}

@Benchmark

public int longAdderTest(Blackhole blackhole) throws InterruptedException{

LongAdder longAdder = new LongAdder();

for (int i = 0; i 

longAdder.add(1);

}

return longAdder.intValue();

}

}

程序执行的结果为:

8a2a6ebe2f28bb93552f4dac55faccf6.png

从上述的数据可以看出,在开启了 1000 个线程之后,程序的 LongAdder 的性能比 AtomicInteger 快了约 1.53 倍,你没看出是开了 1000 个线程,为什么要开这么多呢?这其实是为了模拟高并发高竞争的环境下二者的性能查询。

如果在低竞争下,比如我们开启 100 个线程,测试的结果如下:

d09d2c10289c1f0f3c0ce7ff7e80bbc0.png

结论:从上面结果可以看出,在低竞争的并发环境下 AtomicInteger 的性能是要比 LongAdder 的性能好,而高竞争环境下 LongAdder 的性能比 AtomicInteger 好,当有 1000 个线程运行时,LongAdder 的性能比 AtomicInteger 快了约 1.53 倍,所以各位要根据自己业务情况选择合适的类型来使用。

性能分析

为什么会出现上面的情况?这是因为 AtomicInteger 在高并发环境下会有多个线程去竞争一个原子变量,而始终只有一个线程能竞争成功,而其他线程会一直通过 CAS 自旋尝试获取此原子变量,因此会有一定的性能消耗;而 LongAdder 会将这个原子变量分离成一个 Cell 数组,每个线程通过 Hash 获取到自己数组,这样就减少了乐观锁的重试次数,从而在高竞争下获得优势;而在低竞争下表现的又不是很好,可能是因为自己本身机制的执行时间大于了锁竞争的自旋时间,因此在低竞争下表现性能不如 AtomicInteger。

总结

本文我们测试了 volatile 在多写情况下是非线程安全的,而在低竞争的并发环境下 AtomicInteger 的性能是要比 LongAdder 的性能好,而高竞争环境下 LongAdder 的性能比 AtomicInteger 好,因此我们在使用时要结合自身的业务情况来选择相应的类型。

觉得不错,点个在看~

79a10067060afc7eb59743935811c4ed.gif

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值