Why does Ali recommend using longadder instead of volatile?

The above content has two key points:

But words have no basis. Even if it is said by the lonely big man, we have to prove it, because master Ma said: practice is the only standard for testing truth.

This has its advantages. First, it deepens our understanding of knowledge; Second, the document only says that longadder has higher performance than atomiclong, but how much higher? The article does not say that we can only test it ourselves.

Not much, let's go directly to the official content of this article

Volatile thread safety test

First, let's test the thread safety of volatile in a multi write environment. 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 use the volatile modified count variable + + 10W times. When starting another thread -- 10W times, the normal result should be 0, but the result of our execution is:

Conclusion: from the above results, we can see that volatile is non thread safe in multi write environment, and the test results are consistent with the java development manual.

Next, we use the official jmh (Java micro benchmark harness) of Oracle to test their performance. 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 results of program execution are:

From the above data, it can be seen that after 1000 threads are started, the performance of longadder of the program is about 1.53 times faster than atomicinteger. You can't see that 1000 threads are opened. Why do you open so many threads? This is actually to simulate the performance of both queries in a highly concurrent and highly competitive environment.

If we start 100 threads under low competition, the test results are as follows:

Conclusion: it can be seen from the above results that the performance of atomicinteger is better than that of longadder in low competition concurrent environment, while that of longadder is better than that of atomicinteger in high competition environment. When 1000 threads are running, the performance of longadder is about 1.53 times faster than that of atomicinteger, Therefore, you should choose the appropriate type to use according to your business situation.

performance analysis

Why does this happen? This is because atomicinteger will have multiple threads competing for an atomic variable in a high concurrency environment, and only one thread can compete successfully, while other threads will always try to obtain this atomic variable through CAS spin, so there will be a certain performance consumption; Longadder separates the atomic variable into a cell array, and each thread obtains its own array through hash, which reduces the number of retries of optimistic lock, so as to gain an advantage under high competition; The performance is not very good under low competition, perhaps because the execution time of its own mechanism is greater than the spin time of lock competition, so the performance is not as good as atomicinteger under low competition.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>