java多线程之AtomicLong原子类(JDK8)

引言

有的同学可能会问这个问题,有了Long类型,为什么还要弄一个AtomicLong类出来?因为在32位的操作系统中,64位的Long类型变量会被jvm拆分为两个32位的来操作,因此不具备原子性。而AtomicLong类型可以保证原子性。

1、AtomicLong介绍

AtomicInteger, AtomicLong和AtomicBoolean这3个基本类型的原子类的原理和用法相似。本章以JDK8为版本对AtomicLong基本类型的原子类进行介绍。AtomicLong继承Number抽象类,实现了Serializable接口,操作是线程安全的。

2、分析源码

// 构造函数
AtomicLong()
// 创建值为initialValue的AtomicLong对象
AtomicLong(long initialValue)
// 以原子方式设置当前值为newValue。
final void set(long newValue) 
// 获取当前值
final long get() 
// 以原子方式将当前值减 1,并返回减1后的值。等价于“--num”
final long decrementAndGet() 
// 以原子方式将当前值减 1,并返回减1前的值。等价于“num--”
final long getAndDecrement() 
// 以原子方式将当前值加 1,并返回加1后的值。等价于“++num”
final long incrementAndGet() 
// 以原子方式将当前值加 1,并返回加1前的值。等价于“num++”
final long getAndIncrement()    
// 以原子方式将delta与当前值相加,并返回相加后的值。
final long addAndGet(long delta) 
// 以原子方式将delta添加到当前值,并返回相加前的值。
final long getAndAdd(long delta) 
// 如果当前值 == expect,则以原子方式将该值设置为update。成功返回true,否则返回false,并且不修改原值。
final boolean compareAndSet(long expect, long update)
// 以原子方式设置当前值为newValue,并返回旧值。
final long getAndSet(long newValue)
//以下部分省略……

AtomicLong的方法都比较简单,下面重点分析下incrementAndGet的方法

public final long incrementAndGet() {
     return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
 }
 //unsafe中getAndAddLong实现
 public final long getAndAddLong(Object var1, long var2, long var4) {
     long var6;
     do {
     	//这个this表示了当前对象的地址,后面的valueOffset是在该对象开始的位置加上这个valueOffset的偏移量,就能拿到的是当前对象的值
         var6 = this.getLongVolatile(var1, var2);
     } while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));

     return var6;
}

在getAndAddLong实现中,compareAndSwapLong基于的是CPU 的 CAS指令来实现的,可认为是无阻塞的,一个线程的失败或挂起不会引起其它线程也失败或挂起。但并发量很大的话,cpu会花费大量的时间在试错上面,相当于一个自旋的操作。如果并发量小的情况,这些消耗可以忽略不计。
JDK8中新增了LongAdder,内部的实现有点类似ConcurrentHashMap的分段锁,最好的情况下,每个线程都有独立的计数器,这样可以大量减少并发操作。

3、性能测试

下面通过JMH比较一下AtomicLong 和 LongAdder的性能

@OutputTimeUnit(TimeUnit.MICROSECONDS)
@BenchmarkMode(Mode.AverageTime)
public class AtomicLongTest {
    private static AtomicLong count = new AtomicLong();
    private static LongAdder longAdder = new LongAdder();
    public static void main(String[] args) throws Exception {
        Options options = new OptionsBuilder().include(AtomicLongTest.class.getName()).forks(1).build();
        new Runner(options).run();
    }

    @Benchmark
    @Threads(10)
    public void run0(){
        count.getAndIncrement();
    }

    @Benchmark
    @Threads(10)
    public void run1(){
        longAdder.increment();
    }
}

maven的pom文件依赖包

<dependency>
      <groupId>org.openjdk.jmh</groupId>
      <artifactId>jmh-core</artifactId>
      <version>1.21</version>
</dependency>
<dependency>
      <groupId>org.openjdk.jmh</groupId>
      <artifactId>jmh-generator-annprocess</artifactId>
      <version>1.21</version>
 </dependency>

1、设置BenchmarkMode为Mode.Throughput,测试吞吐量
2、设置BenchmarkMode为Mode.AverageTime,测试平均耗时
3、程序运行直接使用run方法,不支持debug模式

下面是运行的结果:
吞吐量

Benchmark             Mode  Cnt    Score    Error   Units
AtomicLongTest.run0  thrpt    5   34.070 ±  1.830  ops/us
AtomicLongTest.run1  thrpt    5  152.216 ± 45.756  ops/us

平均耗时

Benchmark             Mode  Cnt    Score    Error   Units
AtomicLongTest.run0   avgt    5    0.350 ±  0.063   us/op
AtomicLongTest.run1   avgt    5    0.072 ±  0.029   us/op

从上面可以看出LongAdder的吞吐量和平均耗时均优于AtomicLong

4、总结

普通场景保证线程安全,建议使用AtomicLong,一些高并发的场景,比如限流计数器,建议使用LongAdder替换AtomicLong,以提高性能。

结束语

本篇介绍了AtomicLong的基本原理,比较了LongAdder和AtomicLong之间高并发下的性能,还介绍了一款适合java的JMH性能测试工具,有兴趣的同学可以详细的了解下,下一章将介绍JUC包中的锁。

原创文章 55 获赞 76 访问量 17万+

猜你喜欢

转载自blog.csdn.net/cool_summer_moon/article/details/105902381