AtomicLong
和 LongAdder
都是 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
值的更新是原子的。
- CAS 操作:
AtomicLong
会先读取当前 value
的值(currentValue
),然后基于期望值(expectedValue
)和更新值(newValue
)执行 CAS 操作:
- 如果
currentValue == expectedValue
,则将 value
更新为 newValue
,并返回 true
。
- 否则,返回
false
,表示 CAS 失败。
- 自旋操作:如果 CAS 失败,线程会不断循环重试(自旋),直到成功为止。
以下是 AtomicLong
的 incrementAndGet()
方法的伪代码实现:
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 更新 AtomicLong
的 value
值,会导致以下问题:
- 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 引入的类,专门为高并发场景设计,通过分段累加的方式减少线程竞争。
特点:
- 分段累加:内部维护了一个数组(
Cell[]
),每个线程操作不同的 Cell,最后汇总结果。
- 减少竞争:通过分散竞争点,降低了 CAS 失败的概率。
- 性能:
- 在高并发场景下性能显著优于
AtomicLong
。
- 在低并发场景下,由于分段累加的开销,性能可能不如
AtomicLong
。
- 最终一致性:
LongAdder
的结果在计算过程中并不是精确的,只有在调用 sum()
时才会汇总所有分段的值。
适用场景:
示例:
1
2
3
|
LongAdder adder = new LongAdder();
adder.increment(); // 线程安全的递增操作
long sum = adder.sum(); // 获取当前总值
|
核心区别
特性 |
AtomicLong |
LongAdder |
实现原理 |
基于 CAS 操作,所有线程竞争一个全局变量。 |
分段累加(Cell[]),减少竞争。 |
竞争点 |
所有线程竞争一个变量。 |
线程分散竞争,操作不同的 Cell。 |
性能 |
低并发下性能较好,高并发下性能下降。 |
高并发下性能显著优于 AtomicLong 。 |
结果精确性 |
实时精确。 |
最终一致,sum() 时会汇总结果。 |
内存开销 |
较小。 |
较大(因为需要维护多个 Cell)。 |
适用场景 |
低并发或需要实时精确值的场景。 |
高并发或对实时精确性要求不高的场景。 |
如何选择?
-
低并发场景:
- 如果需要实时精确值,选择
AtomicLong
。
AtomicLong
的性能足以满足需求。
-
高并发场景:
- 如果不需要实时精确值,选择
LongAdder
。
LongAdder
通过减少竞争,显著提升了性能。
-
其他考虑:
- 如果需要频繁读取实时值,
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());
|
在以上代码中:
AtomicLong
的 incrementAndGet()
会频繁竞争同一个变量。
LongAdder
的 increment()
会将操作分散到不同的 Cell,减少竞争。
总结
AtomicLong
适用于低并发或需要实时精确值的场景。
LongAdder
适用于高并发或对实时精确性要求不高的场景。
- 在高并发场景下,
LongAdder
通过分段累加的方式显著减少了线程竞争,提供了更好的性能。