Jun 01, 2021 Article blog
The latest edition of Ali's Java Development Manual was recently released, with a passage that caught the editor's attention as follows:
Reference: volatile solves the problem of multithreaded memory not being visible. For one write and read more, it can solve the variable synchronization problem, but if you write more, it also can not solve the thread security problem.
Description: In the case of a count-plus operation, implement it using the following class: AtomicInteger count-new AtomicInteger(); c ount.addAndGet(1); In the case of JDK8, longAdder objects are recommended for better performance than AtomicLong (reducing the number of retries of optimistic locks).
There are two key points to the above:
volatile
LongAdder
instead of
AtomicLong
instead of
volatile
LongAdder
better.
But there is no reason to say, even if the lonely big man said, we have to confirm, because Mr. Ma said: Practice is the only test of truth.
There are benefits to doing so, first, by deepening our understanding of knowledge, and second, by writing only that
LongAdder
better than
AtomicLong
but how much higher?
It doesn't say that it's up to us to test it ourselves.
(Recommended tutorial: Java tutorial)
Not much, then we go directly to the official content of this article...
First, let's test the thread security situation in a
volatile
overwriting environment, and the test code is as follows:
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); // 打印结果
}
}
We put the
count
variable that
volatile
decorates 10w times, and when we start another thread -- 10w times, normally the result should be 0, but the result of our execution is:
1063
Conclusion: From the above results, it can be seen that
volatile
is non-thread-safe in a multi-write environment, and the test results are consistent with the Java Development Manual.
Next, we use Oracle's official JMH to test the performance of both, and the test code is as follows:
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();
}
}
The result of the execution of the program is:
As you can see from the above data, after opening 1000 threads, the program's
LongAdder
performs about 1.53 times faster than
AtomicInteger
This is actually to simulate the performance queries of both in a highly concurrent and highly competitive environment.
If we open 100 threads at a low level of competition, for example, the results of the test are as follows:
Conclusion: As can be seen from the above results,
AtomicInteger
better than
LongAdder
in a low-competitive concurrent environment, while
LongAdder
performs better than
AtomicInteger
in a highly competitive environment,
and when 1000 threads run,
LongAdder
performs about 1.53 times faster than
AtomicInteger
so you have to choose the right type to use based on your business situation.
Why is this happening?
This is because
AtomicInteger
has multiple threads competing for an atomic variable in a high concurrent environment, and only one thread can always
LongAdder
compete successfully, while other threads will always try to get this atomic variable through
CAS
spin, so there will be some performance consumption Each thread gets its own array through Hash, which reduces the number of retries of optimistic locks to gain an advantage in high competition, while at low competition it does not perform very well, perhaps because its own mechanism takes longer to execute than the spin time of the lock competition, and therefore performs less well than
AtomicInteger
in low competition.
Cell
(Recommended micro-class: Java micro-class)
In this article, we tested
volatile
is non-thread-safe in multi-write situations, while
AtomicInteger
better than
LongAdder
in a low-competitive concurrent environment, and
LongAdder
performs better than
AtomicInteger
in a highly competitive environment,
so we use it to select the appropriate type in the light of our own business situation.
Source: Public Number -- Java Chinese Community Author: Lei ge
Above is
W3Cschool编程狮
about why Ali recommends longAdder instead of volatele?
Related to the introduction, I hope to help you.