Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

Why does Ali recommend longAdder instead of volatele?


Jun 01, 2021 Article blog


Table of contents


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:

  1. Non-one-write, read-read scenarios like count cannot use volatile
  2. If JDK8 recommends 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...

Volatile thread safety test

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.

LongAdder VS AtomicLong

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:

 Why does Ali recommend longAdder instead of volatele?1

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:

 Why does Ali recommend longAdder instead of volatele?2

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.

Performance analysis

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)

summary

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.