LongAdder 相比 AtomicXXX 类性能更好的原因是什么?请解释其工作原理。
参考回答**
LongAdder
相比 AtomicLong
和其他 AtomicXXX
类性能更好,主要是因为它采用了 分段累加(或称为分段锁)机制,减少了线程间的竞争。AtomicLong
在高并发的场景下,多个线程同时更新时会频繁地争用同一个变量,导致性能瓶颈。而 LongAdder
通过将更新操作分散到多个独立的槽中(即 Cell
),每个线程更新不同的槽,从而显著减少了竞争,提高了并发性能。
工作原理:
LongAdder
内部维护了多个Cell
(独立的槽),每个槽都有一个独立的值。- 线程在执行累加时会选择一个独立的槽进行操作,减少了多个线程竞争同一变量的情况。
- 最终获取结果时,会将所有
Cell
的值加总起来,得到最终的累加值。
详细讲解与拓展
1. 为什么 LongAdder
的性能优于 AtomicLong
AtomicLong
的局限性:
AtomicLong
使用 CAS(Compare-and-Swap) 操作来确保原子性。CAS 操作本身是线程安全的,但在高并发的情况下,多个线程可能会争抢对同一变量的访问,导致频繁的自旋和重试。这样,不仅浪费 CPU 时间,也会增加延迟,尤其是在多线程竞争非常激烈时,性能表现较差。
举个例子:假设有 1000 个线程同时对同一个 AtomicLong
变量进行累加操作。每次更新都需要使用 CAS 操作来判断当前值并更新。如果大量线程同时争用这个变量,会产生很多冲突,导致线程反复重试操作,性能会显著下降。
LongAdder
的优化:
LongAdder
通过 分段累加(将累加操作分配到多个独立的槽中)来避免线程间的竞争。内部维护一个 Cell[]
数组,每个 Cell
存储一个值。线程在执行 add
操作时,会选择一个槽进行累加,从而减少对同一 Cell
的竞争。
- 分段设计:
LongAdder
将内部的累加操作分配到多个Cell
中。每个线程可以对不同的Cell
进行累加,而不会争用同一个内存位置。- 这样即使在高并发的情况下,每个
Cell
都有自己的局部更新,不会因为竞争同一个变量导致性能下降。
- 最终合并:
- 当获取累加结果时,
LongAdder
会将所有Cell
的值合并得到最终结果。
- 当获取累加结果时,
举个例子:在 1000 个线程并发执行时,每个线程会对 LongAdder
内部的不同 Cell
执行加法操作,而不是竞争同一个值。通过这种分段累加的方法,LongAdder
能够在高并发情况下提高性能。
2. LongAdder
的工作原理
LongAdder
内部通过 Cell[]
数组来存储多个 Cell
,每个 Cell
都是一个独立的存储单元,用来存放累加值。线程在执行 add
操作时,会通过一些规则选择一个 Cell
来进行更新。最终,LongAdder
会将所有 Cell
的值加起来,得到最终的累加值。
具体步骤:
- 初始化:
LongAdder
初始化时,会创建一个默认大小的Cell[]
数组。 - 累加操作:每次累加时,
LongAdder
会根据一些规则(如线程哈希值)选择一个Cell
来执行add
操作。 - 合并操作:获取总和时,会遍历
Cell[]
数组,将每个Cell
的值加起来。
伪代码:
public class LongAdder {
private volatile Cell[] cells;
public void add(long x) {
// 获取 Cell 数组
Cell[] cells = this.cells;
// 选择一个 Cell 来进行操作
Cell cell = chooseCell(cells);
// 累加
cell.add(x);
}
public long sum() {
long sum = 0;
// 遍历所有 Cell 累加
for (Cell cell : cells) {
sum += cell.value;
}
return sum;
}
}
3. LongAdder
和 AtomicLong
的对比
特性 | AtomicLong |
LongAdder |
---|---|---|
并发性能 | 高并发下可能产生较大性能瓶颈,因频繁竞争相同变量 | 高并发下能显著提高性能,避免了大量线程竞争同一个变量 |
适用场景 | 低并发、少量更新场景 | 高并发、大量更新场景 |
内存占用 | 使用一个单一的值进行存储 | 使用多个 Cell 存储累加值,内存占用稍大 |
累加操作 | 每次更新都通过 CAS 操作更新同一个变量 | 每个线程可能更新不同的槽,减少竞争 |
最终结果 | 获取值时直接读取变量的值 | 获取值时需要将所有 Cell 的值加起来 |
4. 示例代码
示例:AtomicLong
和 LongAdder
的性能对比
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
public class PerformanceTest {
private static final int NUM_THREADS = 1000;
private static final int NUM_ITERATIONS = 1000;
public static void main(String[] args) throws InterruptedException {
// 使用 AtomicLong
AtomicLong atomicLong = new AtomicLong(0);
Thread[] atomicThreads = new Thread[NUM_THREADS];
for (int i = 0; i < NUM_THREADS; i++) {
atomicThreads[i] = new Thread(() -> {
for (int j = 0; j < NUM_ITERATIONS; j++) {
atomicLong.incrementAndGet();
}
});
}
long startTime = System.nanoTime();
for (Thread thread : atomicThreads) thread.start();
for (Thread thread : atomicThreads) thread.join();
long atomicDuration = System.nanoTime() - startTime;
// 使用 LongAdder
LongAdder longAdder = new LongAdder();
Thread[] longAdderThreads = new Thread[NUM_THREADS];
for (int i = 0; i < NUM_THREADS; i++) {
longAdderThreads[i] = new Thread(() -> {
for (int j = 0; j < NUM_ITERATIONS; j++) {
longAdder.add(1);
}
});
}
startTime = System.nanoTime();
for (Thread thread : longAdderThreads) thread.start();
for (Thread thread : longAdderThreads) thread.join();
long longAdderDuration = System.nanoTime() - startTime;
System.out.println("AtomicLong duration: " + atomicDuration + " ns");
System.out.println("LongAdder duration: " + longAdderDuration + " ns");
}
}
5. 总结
LongAdder
的优势:- 通过 分段累加,减少了多个线程竞争同一个变量的情况,极大提高了高并发情况下的性能。
- 适用于需要频繁累加的高并发场景。
AtomicLong
:- 适用于低并发或更新较少的场景,适合单一变量的原子更新。
- 在高并发情况下,由于 CAS 的竞争,可能会造成性能瓶颈。