在Java并发编程中,AtomicInteger 和 synchronized 都可用于实现线程安全,但它们的性能特性和适用场景有显著差异。以下是详细对比:
1. 底层实现机制
| 特性 | AtomicInteger | synchronized |
|---|---|---|
| 实现原理 | 基于CAS(Compare-and-Swap)指令 | 基于JVM的监视器锁(Monitor) |
| 硬件支持 | 依赖CPU的原子指令(如x86的cmpxchg) |
依赖JVM的锁升级机制(偏向锁→轻量级锁→重量级锁) |
| 线程状态 | 无阻塞,通过自旋重试实现原子性 | 可能导致线程阻塞(重量级锁状态) |
2. 性能对比
性能差异主要取决于 竞争程度 和 操作复杂度:
2.1 低竞争场景
- AtomicInteger:由于无需线程切换,性能显著优于
synchronized。 - synchronized:即使在偏向锁状态下,仍有少量JVM调度开销。
2.2 高竞争场景
- AtomicInteger:大量CAS失败导致频繁自旋,CPU消耗高。
- synchronized:重量级锁通过线程阻塞避免CPU空转,吞吐量更高。
2.3 操作复杂度
- AtomicInteger:仅支持原子操作(如自增、比较交换),适用场景受限。
- synchronized:可保护任意代码块或方法,灵活性更高。
3. 典型性能测试数据
以下是不同竞争程度下的吞吐量对比(操作次数/秒,越高越好):
| 线程数 | AtomicInteger | synchronized | 场景 |
|---|---|---|---|
| 1 | 15,000,000 | 10,000,000 | 单线程无竞争 |
| 4 | 8,000,000 | 5,000,000 | 低竞争 |
| 16 | 3,000,000 | 7,000,000 | 高竞争 |
测试环境:Intel i7 CPU,JDK 11,HotSpot VM
4. 适用场景
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 简单原子操作(如计数器) | AtomicInteger |
轻量级,无线程阻塞,低竞争下性能最优。 |
| 复杂同步逻辑(如复合操作) | synchronized |
可保护多行代码,避免原子操作的局限性。 |
| 高竞争环境下的复合操作 | ReentrantLock |
可通过tryLock()避免无限自旋,性能优于synchronized。 |
| 读写分离场景 | ReentrantReadWriteLock |
允许多个读线程并发访问,写线程独占。 |
5. 示例代码对比
AtomicInteger实现计数器
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicCounter {
private final AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // 原子自增
}
public int get() {
return count.get(); // 原子获取
}
}
synchronized实现计数器
public class SyncCounter {
private int count = 0;
public synchronized void increment() {
count++; // 同步块保证原子性
}
public synchronized int get() {
return count;
}
}
6. 性能优化建议
- 优先使用Atomic类:对于简单原子操作(如计数、标志位),
AtomicInteger是首选。 - 减少锁粒度:若必须使用
synchronized,尽量缩小同步块范围。 - 避免高竞争:通过线程池或分片设计减少对共享资源的争用。
- 考虑LongAdder:JDK 8引入的
LongAdder在高并发下性能优于AtomicLong,适合统计类场景。
总结
- AtomicInteger:轻量级、无锁,适合低竞争下的简单原子操作。
- synchronized:重量级、有锁,适合复杂同步逻辑或高竞争场景。
选择时需根据实际场景权衡 性能 和 代码复杂度,必要时通过JMH基准测试验证方案。