题目
设计一个线程安全的计数器,支持高并发自增和获取当前值
信息
- 类型:问答
- 难度:⭐⭐
考点
线程安全,原子操作,并发容器,性能优化
快速回答
实现线程安全计数器的核心方案:
- 使用
AtomicLong或LongAdder(JDK8+)实现原子操作 - 避免使用
synchronized或volatile+锁的原始方案 - 高并发场景优先选择
LongAdder减少竞争 - 获取最终值时注意调用
sum()方法合并单元格
问题背景
在高并发场景下设计计数器需解决两个核心问题:1)多线程自增操作的原子性;2)保证获取当前值的可见性。常见错误方案如使用volatile long或synchronized方法会导致性能瓶颈或数据不一致。
解决方案对比
方案1:AtomicLong(基础版)
import java.util.concurrent.atomic.AtomicLong;
public class AtomicCounter {
private final AtomicLong count = new AtomicLong(0);
public void increment() {
count.incrementAndGet();
}
public long getCount() {
return count.get();
}
}原理说明:基于CAS(Compare-And-Swap)实现无锁更新,适合低竞争场景。但在高并发下大量线程重试会导致CPU资源浪费。
方案2:LongAdder(JDK8+ 优化版)
import java.util.concurrent.atomic.LongAdder;
public class AdderCounter {
private final LongAdder count = new LongAdder();
public void increment() {
count.increment();
}
public long getCount() {
return count.sum();
}
}原理说明:采用分段累加(Cell数组)分散竞争,线程优先更新自己的Cell,最后合并结果。吞吐量比AtomicLong高5倍以上(基准测试数据)。
最佳实践
- 首选LongAdder:适用于写多读少的高并发场景(如统计点击数)
- 精确场景用AtomicLong:需要实时精确值且竞争不激烈时使用
- 避免误区:
- 错误1:
volatile long + synchronized➜ 锁竞争导致性能骤降 - 错误2:直接调用
LongAdder.longValue()➜ 实际应调用sum()
- 错误1:
性能测试数据(参考)
| 线程数 | AtomicLong (ops/ms) | LongAdder (ops/ms) |
|---|---|---|
| 1 | 15,000 | 8,000 |
| 10 | 2,500 | 45,000 |
| 100 | 300 | 52,000 |
扩展知识
- CAS的ABA问题:AtomicLong可能遇到,但计数器场景不影响正确性
- 最终一致性:LongAdder的
sum()结果非实时精确值,但保证最终正确 - 分布式计数器:超大规模系统可考虑Redis原子操作或分片统计