The introduction
Some of you might ask the question, if you have Long, why do you have an AtomicLong? Because on a 32-bit operating system, 64-bit Long variables are split by the JVM into two 32-bit ones, they don’t have atomicity. The AtomicLong type guarantees atomicity.
1. Introduction to AtomicLong
The three basic types of atomic classes AtomicInteger, AtomicLong, and AtomicBoolean are similar in principle and usage. This chapter introduces the basic type of AtomicLong in version JDK8. AtomicLong inherits the Number abstract class and implements the Serializable interface, with thread-safe operations.
2. Analyze the source code
// constructor
AtomicLong()
// Create an AtomicLong object with initialValue
AtomicLong(long initialValue)
// Atomically set the current value to newValue.
final void set(long newValue)
// Get the current value
final long get(a)
// Atomically subtracts the current value by 1 and returns the value. Equivalent to the "num"
final long decrementAndGet(a)
// Atomically subtracts the current value by 1 and returns the value before subtracting 1. Equivalent to the "num -
final long getAndDecrement(a)
// Atomically increments the current value by 1 and returns the incremented value. Is equivalent to "+ + num"
final long incrementAndGet(a)
// Increments the current value atomically and returns the value before incrementing by 1. Is equivalent to "num++"
final long getAndIncrement(a)
// Add delta atomically to the current value and return the added value.
final long addAndGet(long delta)
// Add delta atomically to the current value and return the value before the addition.
final long getAndAdd(long delta)
// If the current value == expect, set it atomically to update. Returns true on success, false otherwise, and does not modify the original value.
final boolean compareAndSet(long expect, long update)
// Atomically sets the current value to newValue and returns the old value.
final long getAndSet(long newValue)
// The following part is omitted...
Copy the code
AtomicLong’s methods are relatively simple, so let’s focus on the incrementAndGet method
public final long incrementAndGet(a) {
return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
}
// Unsafe implementation of getAndAddLong
public final long getAndAddLong(Object var1, long var2, long var4) {
long var6;
do {
// This represents the address of the current object, and valueOffset is the value of the current object by adding the offset of valueOffset to the start of the object
var6 = this.getLongVolatile(var1, var2);
} while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
return var6;
}
Copy the code
In the getAndAddLong implementation, compareAndSwapLong is implemented based on the CAS instruction of the CPU and can be considered non-blocking. The failure or suspension of one thread does not cause other threads to fail or suspend. But with a lot of concurrency, the CPU spends a lot of time on trial and error, the equivalent of a spin operation. If concurrency is small, these costs can be ignored. LongAdder has been added in JDK8. The internal implementation is somewhat similar to the piecelock of ConcurrentHashMap, and in the best case, each thread has its own counter, which greatly reduces concurrent operations.
3. Performance test
The following compares the performance of AtomicLong and LongAdder using JMH
@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(a){
count.getAndIncrement();
}
@Benchmark
@Threads(10)
public void run1(a){ longAdder.increment(); }}Copy the code
Maven’s POM file dependency package
<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>
Copy the code
Set BenchmarkMode to mode. Throughput. Set BenchmarkMode to mode. AverageTime, test AverageTime
Here is the result of the run: throughput
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
Copy the code
The average time
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
Copy the code
As you can see from the above, LongAdder has better throughput and average time than AtomicLong
4, summarize
AtomicLong is recommended for common scenarios to ensure thread-safety, and LongAdder is recommended to replace AtomicLong for high concurrency scenarios, such as stream limiting counters, to improve performance.
conclusion
This article introduces the basic principles of AtomicLong, compares the performance of LongAdder and AtomicLong under high concurrency, also introduces a JMH performance test tool suitable for Java, interested students can understand in detail, the next chapter will introduce the JUC package lock.
If you found this post helpful, please give it a thumbs up and a follow.