为什么使用LongAdder而不是volatile
今天小编给大家分享一下为什么使用LongAdder而不是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
接下来,我们使用 Oracle 官方的 JMH(Java Microbenchmark Harness, JAVA 微基准测试套件)来测试一下两者的性能,测试代码如下:
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 < 1024; 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 < 1024; i++) { longAdder.add(1); } return longAdder.intValue(); } }
从上述的数据可以看出,在开启了 1000 个线程之后,程序的 LongAdder
的性能比 AtomicInteger
快了约 1.53 倍,你没看出是开了 1000 个线程,为什么要开这么多呢?这其实是为了模拟高并发高竞争的环境下二者的性能查询。
如果在低竞争下,比如我们开启 100 个线程
结论:从上面结果可以看出,在低竞争的并发环境下AtomicInteger
的性能是要比 LongAdder
的性能好,而高竞争环境下 LongAdder
的性能比 AtomicInteger
好,当有 1000 个线程运行时,LongAdder
的性能比 AtomicInteger
快了约 1.53 倍,所以各位要根据自己业务情况选择合适的类型来使用。
性能分析
为什么会出现上面的情况?这是因为 AtomicInteger
在高并发环境下会有多个线程去竞争一个原子变量,而始终只有一个线程能竞争成功,而其他线程会一直通过 CAS 自旋尝试获取此原子变量,因此会有一定的性能消耗;而 LongAdder
会将这个原子变量分离成一个 Cell 数组,每个线程通过 Hash 获取到自己数组,这样就减少了乐观锁的重试次数,从而在高竞争下获得优势;而在低竞争下表现的又不是很好,可能是因为自己本身机制的执行时间大于了锁竞争的自旋时间,因此在低竞争下表现性能不如 AtomicInteger
。
以上就是“为什么使用LongAdder而不是volatile”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注创新互联行业资讯频道。
网页题目:为什么使用LongAdder而不是volatile
文章位置:http://ybzwz.com/article/peogop.html