返回
Featured image of post AtomicLong和LongAdder

AtomicLong和LongAdder

AtomicLongLongAdder 都是 Java 中用于高并发场景下对长整型变量进行操作的类,但它们的设计目标和实现方式有显著区别

AtomicLong

AtomicLong 是 Java 中用于原子更新长整型变量的类,基于 CAS(Compare-And-Swap)实现。

### 特点 1. **原子操作**:通过 CAS 保证操作的原子性。 2. **单一变量**:所有线程共享一个全局变量,竞争集中在这一点。 3. **性能**: - 在低并发场景下性能较好。 - 在高并发场景下,由于所有线程竞争同一个变量,CAS 失败率高,性能下降。 4. **适用场景:** - 低并发场景。 - 需要精确的全局计数。

CAS + 自旋操作更新 AtomicLong 中的 value 值

在 Java 中,AtomicLong 是基于 CAS(Compare-And-Swap)机制实现的,用于在多线程环境下对长整型变量进行原子性操作。它的核心思想是通过 CAS + 自旋的方式,确保对 value 值的更新是原子的。

  1. CAS 操作AtomicLong 会先读取当前 value 的值(currentValue),然后基于期望值(expectedValue)和更新值(newValue)执行 CAS 操作:
    • 如果 currentValue == expectedValue,则将 value 更新为 newValue,并返回 true
    • 否则,返回 false,表示 CAS 失败。
  2. 自旋操作:如果 CAS 失败,线程会不断循环重试(自旋),直到成功为止。

以下是 AtomicLongincrementAndGet() 方法的伪代码实现:

1
2
3
4
5
6
7
8
9
public long incrementAndGet() {
    long currentValue;
    long newValue;
    do {
        currentValue = get();            // 获取当前值
        newValue = currentValue + 1;    // 计算新值
    } while (!compareAndSet(currentValue, newValue)); // CAS + 自旋直到成功
    return newValue;
}

在这个实现中:

  • get() 方法获取当前 value 的值(currentValue)。
  • compareAndSet(currentValue, newValue) 尝试通过 CAS 更新 value
  • 如果 CAS 失败,线程会自旋重试,直到成功为止。

AtomicLong 瓶颈分析

尽管 AtomicLong 在低并发场景下性能良好,但在高并发环境下,它的性能会显著下降,主要原因是 CAS + 自旋机制在高竞争下成为瓶颈。

在高并发环境下,多个线程同时尝试通过 CAS 更新 AtomicLongvalue 值,会导致以下问题:

  • CAS 失败率高
    • 由于所有线程都竞争同一个 value 变量,CAS 操作的成功概率会急剧下降。
    • 一个线程成功更新后,其他线程的 CAS 操作都会失败,导致大量线程需要自旋重试。
  • 自旋开销大
    • 自旋是一种忙等待(Busy-Waiting),线程会持续占用 CPU 资源。
    • 在高竞争下,大量线程会不断自旋,导致 CPU 利用率过高,甚至可能影响其他任务的执行。
  • 性能瓶颈
    • 随着并发线程数的增加,AtomicLong 的性能会显著下降,成为系统的瓶颈。

性能实证分析

我们测试二者在1/10/100线程下的性能

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**
 * AtomicLong 和 LongAdder 耗时测试
 */
public class AtomicLongAdderTest {
    public static void main(String[] args) throws Exception {
        test(1, 10000000);  // 1 线程,1000 万次操作
        test(10, 10000000); // 10 线程,1000 万次操作
        test(100, 10000000); // 100 线程,1000 万次操作
    }

    static void test(int threadCount, int times) throws Exception {
        System.out.println("threadCount: " + threadCount + ", times: " + times);
        
        // 测试 LongAdder
        long start = System.currentTimeMillis();
        testLongAdder(threadCount, times);
        System.out.println("LongAdder 耗时:" + (System.currentTimeMillis() - start) + "ms");

        // 测试 AtomicLong
        start = System.currentTimeMillis();
        testAtomicLong(threadCount, times);
        System.out.println("AtomicLong 耗时:" + (System.currentTimeMillis() - start) + "ms");
        System.out.println("----------------------------------------");
    }

    static void testAtomicLong(int threadCount, int times) throws Exception {
        AtomicLong atomicLong = new AtomicLong();
        List<Thread> threads = new ArrayList<>();

        for (int i = 0; i < threadCount; i++) {
            threads.add(new Thread(() -> {
                for (int j = 0; j < times; j++) {
                    atomicLong.incrementAndGet();
                }
            }));
        }

        for (Thread thread : threads) thread.start();
        for (Thread thread : threads) thread.join();

        System.out.println("AtomicLong value: " + atomicLong.get());
    }

    static void testLongAdder(int threadCount, int times) throws Exception {
        LongAdder longAdder = new LongAdder();
        List<Thread> threads = new ArrayList<>();

        for (int i = 0; i < threadCount; i++) {
            threads.add(new Thread(() -> {
                for (int j = 0; j < times; j++) {
                    longAdder.increment();
                }
            }));
        }

        for (Thread thread : threads) thread.start();
        for (Thread thread : threads) thread.join();

        System.out.println("LongAdder value: " + longAdder.longValue());
    }
}

得到以下结论

AtomicLong LongAdder
1 58ms 169ms
10 1861ms 207ms
100 18509ms 1864ms
这里可以看到随着并发的增加,AtomicLong性能是急剧下降的,耗时是LongAdder的数倍。

总结

  • AtomicLong 通过 CAS + 自旋的方式实现对 value 值的原子性更新。
  • 在高并发环境下,由于线程竞争激烈,AtomicLong 的 CAS 失败率高,自旋开销大,成为性能瓶颈。
  • 优化方案包括使用 LongAdder、减少共享资源竞争以及使用其他无锁数据结构。
  • 在高并发场景下,推荐使用 LongAdder,以减少竞争,提升性能。

示例

1
2
AtomicLong counter = new AtomicLong(0);
counter.incrementAndGet(); // 线程安全的递增操作

LongAdder

LongAdder 是 Java 8 引入的类,专门为高并发场景设计,通过分段累加的方式减少线程竞争。

特点

  1. 分段累加:内部维护了一个数组(Cell[]),每个线程操作不同的 Cell,最后汇总结果。
  2. 减少竞争:通过分散竞争点,降低了 CAS 失败的概率。
  3. 性能
    • 在高并发场景下性能显著优于 AtomicLong
    • 在低并发场景下,由于分段累加的开销,性能可能不如 AtomicLong
  4. 最终一致性LongAdder 的结果在计算过程中并不是精确的,只有在调用 sum() 时才会汇总所有分段的值。

适用场景

  • 高并发场景。
  • 不要求实时精确值,只需要最终结果。

示例

1
2
3
LongAdder adder = new LongAdder();
adder.increment(); // 线程安全的递增操作
long sum = adder.sum(); // 获取当前总值

核心区别

特性 AtomicLong LongAdder
实现原理 基于 CAS 操作,所有线程竞争一个全局变量。 分段累加(Cell[]),减少竞争。
竞争点 所有线程竞争一个变量。 线程分散竞争,操作不同的 Cell。
性能 低并发下性能较好,高并发下性能下降。 高并发下性能显著优于 AtomicLong
结果精确性 实时精确。 最终一致,sum() 时会汇总结果。
内存开销 较小。 较大(因为需要维护多个 Cell)。
适用场景 低并发或需要实时精确值的场景。 高并发或对实时精确性要求不高的场景。

如何选择?

  1. 低并发场景

    • 如果需要实时精确值,选择 AtomicLong
    • AtomicLong 的性能足以满足需求。
  2. 高并发场景

    • 如果不需要实时精确值,选择 LongAdder
    • LongAdder 通过减少竞争,显著提升了性能。
  3. 其他考虑

    • 如果需要频繁读取实时值,AtomicLong 更适合。
    • 如果只需要最终结果(如统计值),LongAdder 是更好的选择。

示例对比

AtomicLong

1
2
3
4
5
6
7
8
9
AtomicLong counter = new AtomicLong(0);
for (int i = 0; i < 1000; i++) {
    new Thread(() -> {
        for (int j = 0; j < 1000; j++) {
            counter.incrementAndGet();
        }
    }).start();
}
System.out.println("Final count (AtomicLong): " + counter.get());

LongAdder

1
2
3
4
5
6
7
8
9
LongAdder adder = new LongAdder();
for (int i = 0; i < 1000; i++) {
    new Thread(() -> {
        for (int j = 0; j < 1000; j++) {
            adder.increment();
        }
    }).start();
}
System.out.println("Final count (LongAdder): " + adder.sum());

在以上代码中:

  • AtomicLongincrementAndGet() 会频繁竞争同一个变量。
  • LongAdderincrement() 会将操作分散到不同的 Cell,减少竞争。

总结

  • AtomicLong 适用于低并发或需要实时精确值的场景。
  • LongAdder 适用于高并发或对实时精确性要求不高的场景。
  • 在高并发场景下,LongAdder 通过分段累加的方式显著减少了线程竞争,提供了更好的性能。

© 2023 - 2025 壹壹贰捌· 0Days
共书写了265.7k字·共 93篇文章 京ICP备2023035941号-1