Java高频面试之并发编程-27
hello啊,各位观众姥爷们!!!本baby今天又来报道了!哈哈哈哈哈嗝🐶
面试:详细说说AtomicInteger 的原理
AtomicInteger 的原理详解
AtomicInteger
是 Java 并发包 (java.util.concurrent.atomic
) 中的原子类,用于实现 线程安全的整型变量操作(如 i++
),其核心原理基于 CAS(Compare and Swap) 无锁算法和 volatile 可见性。以下是其实现原理的详细分析:
1. 核心设计
(1) 依赖的底层技术
- CAS 操作:通过
sun.misc.Unsafe
类调用 CPU 的原子指令(如CMPXCHG
),实现无锁的原子更新。 - volatile 变量:内部值
value
用volatile
修饰,保证多线程间的可见性(直接读写主内存)。
(2) 类结构关键代码
public class AtomicInteger extends Number implements java.io.Serializable {private static final long serialVersionUID = 6214790243416807050L;// 使用 Unsafe 类操作底层 CASprivate static final Unsafe unsafe = Unsafe.getUnsafe();private static final long valueOffset; // value 字段的内存偏移量static {try {// 获取 value 字段在对象内存中的偏移量valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));} catch (Exception ex) { throw new Error(ex); }}private volatile int value; // 实际存储值的 volatile 变量public AtomicInteger(int initialValue) {value = initialValue;}
}
2. 关键方法实现原理
(1) incrementAndGet()
:原子自增
public final int incrementAndGet() {return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}// Unsafe 类的实现
public final int getAndAddInt(Object o, long offset, int delta) {int v;do {v = getIntVolatile(o, offset); // 读取当前值} while (!compareAndSwapInt(o, offset, v, v + delta)); // CAS 更新return v;
}
步骤:
- 读取当前值:通过
getIntVolatile
读取value
的当前值(volatile
保证可见性)。 - CAS 更新:尝试用
compareAndSwapInt
将value
从v
更新为v + delta
。 - 失败重试:若 CAS 失败(值已被其他线程修改),循环重试直到成功。
(2) compareAndSet()
:CAS 核心方法
public final boolean compareAndSet(int expect, int update) {return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
- 若当前值等于
expect
,则将其更新为update
,返回true
;否则返回false
。
(3) get()
和 set()
:直接访问 volatile 变量
public final int get() {return value; // 直接读取 volatile 变量,保证可见性
}public final void set(int newValue) {value = newValue; // 直接写入 volatile 变量
}
3. 无锁设计的优势
(1) 高性能
- 无阻塞:线程通过自旋(循环重试)而非阻塞等待,减少上下文切换开销。
- 低竞争时高效:在低并发场景下,CAS 成功率极高,性能远超
synchronized
或ReentrantLock
。
(2) 避免死锁
- 无锁机制天然避免死锁,因为没有线程会持有锁不放。
4. 局限性及应对
(1) ABA 问题
- 问题描述:若变量的值从
A
改为B
后又改回A
,CAS 无法感知中间变化。 - 解决方案:使用
AtomicStampedReference
或AtomicMarkableReference
,通过版本号标记状态。AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(0, 0); ref.compareAndSet(0, 1, 0, 1); // 检查值 + 版本号
(2) 自旋开销
- 问题:高并发下 CAS 失败率高,线程长时间自旋浪费 CPU。
- 优化策略:
- 退避算法:失败后等待一段时间再重试(如
Thread.yield()
)。 - 结合锁机制:在自旋一定次数后转为阻塞锁。
- 退避算法:失败后等待一段时间再重试(如
(3) 仅支持单一变量
- 问题:无法原子更新多个变量(如
i++
和j++
需同时保证原子性)。 - 解决方案:
- 使用
AtomicReference
封装多个变量为一个对象。 - 对复合操作使用锁(如
synchronized
)。
- 使用
5. 性能对比
AtomicInteger
vs synchronized
vs ReentrantLock
场景 | AtomicInteger | synchronized | ReentrantLock |
---|---|---|---|
低竞争 | 极快(无锁自旋) | 较快(偏向锁优化) | 较快(CAS 尝试) |
高竞争 | 较差(自旋开销大) | 较差(线程阻塞频繁) | 较差(CAS 竞争激烈) |
复合操作 | 不支持 | 支持 | 支持 |
6. 应用场景
- 计数器:如统计请求量、在线人数。
- 状态标志:如开关状态的原子切换。
- 序列生成:生成唯一递增 ID。
总结
AtomicInteger
通过 CAS + volatile 实现了无锁的线程安全操作:
- CAS:保证原子性,避免锁开销。
- volatile:保证可见性,确保线程读取最新值。
- 自旋重试:在竞争不激烈时高效,高竞争时需结合退避策略。
你想要的技术资料我全都有:https://pan.q删掉汉子uark.cn/s/aa7f2473c65b