summary
AtomicLong is the author of Doug Lea in jdk1 Version 5 is released in Java util. concurrent. Classes under atomic concurrent package. LongAdder is a class released by Doug Lea in java8.
Why do you need LongAdder with AtomicLong?
Here, we have to analyze the shortcomings of AtomicLong.
Let's take a look at atomiclong Source code of incrementandget() method
/** * Atomically increments by one the current value. * * @return the updated value */ public final long incrementAndGet() { return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L; }
Then trace the getAndAddLong method of the Unsafe class
It can be clearly seen that AtomicLong's atomic self increment operation is realized through CAS.
This is appropriate when multithreading competition is not fierce. However, if the thread competition is fierce, a large number of threads will spin in place and keep trying to modify the value, but they always find that the value has been modified, so they continue to spin. This wastes a lot of CPU resources.
And because AtomicLong holds Member variable value is modified by volatile keyword, which is modified by thread Critical resources After, you need to refresh to other threads, which also takes some effort.
Draw a picture to understand:
LongAdder also has one volatile Modified base value, but when the competition is fierce, multiple threads will not always spin to modify this value, but adopt the idea of segmentation. When the competition is fierce, each thread will be scattered and accumulated into an array object element of its corresponding Cell [] array instead of sharing one.
In this way, different threads can be modified in different cells to reduce the competition for critical resources. In essence, it is to trade space for time.
Performance comparison between LongAdder and AtomicLong
After analyzing for a long time, there is no evidence, but it is still unconvincing. Only by proving it can we be convinced.
Next, I will create a fixed thread pool with a capacity of 1000, and then submit threads 100 times the capacity of the thread pool. In each thread, I will + 1 operate on the critical resources. After all threads are executed, count the running time and close it Thread pool . Critical resources are represented by AtomicLong and LongAdder respectively to compare the differences between them.
First, try to make each thread perform 100 + 1 operations. The final cumulative result should be: number of threads × 100=1,000 × one hundred × 100 = 10,000,000
AtomicLongDemo.java
import java.text.NumberFormat; import java.util.ArrayList; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicLong; /** * <pre> * Purpose of the program: to demonstrate the poor performance of AtomicInteger and AtomicLong in high and low performance * AtomicLong is used in 16 threads. * Every time the value changes, it will be refreshed back to the main memory. When the competition is fierce, such flush and refresh operations consume a lot of resources, and CAS will often fail * </pre> * created at 2020/8/11 06:11 * @author lerry */ public class AtomicLongDemo { /** * Number of threads in the thread pool */ final static int POOL_SIZE = 1000; public static void main(String[] args) throws InterruptedException { long start = System.currentTimeMillis(); AtomicLong counter = new AtomicLong(0); ExecutorService service = Executors.newFixedThreadPool(POOL_SIZE); ArrayList<Future> futures = new ArrayList<>(POOL_SIZE); for (int i = 0; i < POOL_SIZE * 100; i++) { futures.add(service.submit(new Task(counter))); } // Wait for all threads to finish executing for (Future future : futures) { try { future.get(); } catch (ExecutionException e) { e.printStackTrace(); } } NumberFormat numberFormat = NumberFormat.getInstance(); System.out.printf("The statistical results are:[%s]n", numberFormat.format(counter.get())); System.out.printf("Time consuming:[%d]millisecond", (System.currentTimeMillis() - start)); // Close thread pool service.shutdown(); } /** * There is an AtomicLong member variable that performs + 1 operations N times at a time */ static class Task implements Runnable { private final AtomicLong counter; public Task(AtomicLong counter) { this.counter = counter; } /** * Each thread performs N + 1 operations */ @Override public void run() { for (int i = 0; i < 100; i++) { counter.incrementAndGet(); } }// end run }// end class }
LongAdderDemo.java
import java.text.NumberFormat; import java.util.ArrayList; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.atomic.LongAdder; /** * <pre> * Purpose of the program: To compare the performance with AtomicLong * </pre> * created at 2020/8/11 06:25 * @author lerry */ public class LongAdderDemo { /** * Number of threads in the thread pool */ final static int POOL_SIZE = 1000; public static void main(String[] args) throws InterruptedException { long start = System.currentTimeMillis(); LongAdder counter = new LongAdder(); ExecutorService service = Executors.newFixedThreadPool(POOL_SIZE); ArrayList<Future> futures = new ArrayList<>(POOL_SIZE); for (int i = 0; i < POOL_SIZE * 100; i++) { futures.add(service.submit(new LongAdderDemo.Task(counter))); } // Wait for all threads to finish executing for (Future future : futures) { try { future.get(); } catch (ExecutionException e) { e.printStackTrace(); } } NumberFormat numberFormat = NumberFormat.getInstance(); System.out.printf("The statistical results are:[%s]n", numberFormat.format(counter.sum())); System.out.printf("Time consuming:[%d]millisecond", (System.currentTimeMillis() - start)); // Close thread pool service.shutdown(); } /** * There is a LongAdder member variable, which performs + 1 operations N times at a time */ static class Task implements Runnable { private final LongAdder counter; public Task(LongAdder counter) { this.counter = counter; } /** * Each thread performs N + 1 operations */ @Override public void run() { for (int i = 0; i < 100; i++) { counter.increment(); } }// end run }// end class }
Note: the screenshots of AtomicLong's running results and LongAdder's running results are put together. AtomicLong's is above and LongAdder's is below.
The result of 100 cumulative runs per thread
As you can see, AtomicLong takes 516 milliseconds and LongAdder takes 438 milliseconds ➗ 438 ≈ 1.18, the former takes a little more than twice as long as the latter. The difference doesn't seem very big.
1000 times accumulation And?
As you can see, AtomicLong takes 3034 milliseconds and LongAdder takes 575 milliseconds ➗ 575 ≈ 5.28, the former takes more than 5 times as long as the latter. The difference began to become obvious.
How about 10000 accumulations?
As you can see, AtomicLong takes 30868 milliseconds and LongAdder takes 2167 milliseconds ➗ 2167 ≈ 14.24, the former takes more than 14 times as long as the latter. The gap has widened.
50000 accumulations?
As you can see, AtomicLong takes 148375 milliseconds, and LongAdder takes 9754 milliseconds ➗ 9754 ≈ 15.21, the former takes more than 15 times as long as the latter. The gap widened further.
conclusion
When the number of accumulations executed by each thread increases, the performance advantage of LongAdder over AtomicLong becomes more and more obvious.
LongAdder adopts the segmentation concept to reduce the competition and conflict between threads, while AtomicLong affects the performance because multiple threads compete for the same value in parallel.
In the case of low competition, AtomicLong and LongAdder have similar characteristics, and the throughput is also similar because the competition is not high.
However, in the case of fierce competition, the expected throughput of LongAdder is much higher. After testing, the throughput of LongAdder is about ten times that of AtomicLong, but everything has to pay a price. While ensuring efficiency, LongAdder also needs to consume more space.
Environmental description
java version "1.8.0_251" Java(TM) SE Runtime Environment (build 1.8.0_251-b08) Java HotSpot(TM) 64-Bit Server VM (build 25.251-b08, mixed mode)
- OS: macOS High Sierra 10.13.4