引言
有的同学可能会问这个问题,有了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包中的锁。