Java原子类之AtomicInteger源码分析
一、简介
AtomicInteger
应该是atomic
框架中用得最多的原子类了。顾名思义,AtomicInteger
是Integer
类型的线程安全原子类,可以在应用程序中以原子的方式更新int
值。
AtomicInteger
的本质:自旋锁 + CAS
原子操作。原子操作是多个线程同时执行,确保其是安全的,且并不需要synchronized
关键字。
本质上,原子操作严重依赖于CAS
,它是由多数现代CPU
直接支持的原子指令。这些指令通常比同步块要快。所以在只需要并发修改单个可变变量的情况下,建议优先使用原子类,而不是使用锁机制实现。
二、案例
从a++
说起为什么使用AtomicInteger
?java
并发机制中主要有三个特性需要去考虑,原子性、可见性和有序性。synchronized
关键字可以保证可见性和有序性却无法保证原子性。而这个AtomicInteger
的作用就是为了保证原子性。我们先看一个例子。
public class TestAtomicInteger {
//定义一个变量
private static volatile int a = 0;
public static void integerAdd() {
Thread[] threads = new Thread[5];
//定义5个线程池,每个线程增加10
for (int i = 0; i < 5; i++) {
threads[i] = new Thread(() -> {
try {
for (int j = 0; j < 10; j++) {
System.out.println("线程:" + Thread.currentThread().getName()
+ ",结果:" + a++);
Thread.sleep(500);
}
} catch (Exception e) {
e.printStackTrace();
}
});
threads[i].start();
}
}
public static void main(String[] args) {
integerAdd();
}
}
在上面的例子中,定义了一个变量a
。并且使用了5
个线程分别去增加。为了保证可见性和有序性我们使用了volatile
关键字对a
进行修饰。如果我们第一次接触的话肯定会觉得5
个线程,每个线程加10
,最后结果一定是50
呀。运行结果如下:
...
线程:Thread-0,结果:42
线程:Thread-2,结果:44
线程:Thread-4,结果:45
线程:Thread-1,结果:46
很明显,可能跟你想象的不一样。为什么会出现这个问题呢?这是因为变量a
虽然保证了可见性和有序性,但是缺没有保证原子性。其原因我们可以来分析一下。
对于a++
的操作,其实可以分解为3个步骤。
(1)从主存中读取a的值
(2)对a进行加1操作
(3)把a重新刷新到主存
volatile
可以保证可见性,但是无法保证原子性。volatile
修饰的变量a
做++操作时,实际的指令包括三个(得到a的值,a+1,将a+1赋值给a),因此可能会出现多个线程交叉执行的结果。有三种方法可以解决:
- 使用
synchronize
修饰a++
代码块
// 修饰的变量为a
synchronize(TestAtomicInteger.class) {
a++;
}
具体请参考Synchronized实现原理
- 使用锁
ReentrantLock
ReentrantLock reentrantLock = new ReentrantLock();
private volatile a = 0;
public void addA() {
// 上锁
reentrantLock.lock();
try {
a++;
} finally {
// 保证锁的释放
reentrantLock.unlock();
}
}
- 使用
JUC
包下的原子类AtomicInteger
public class TestAtomicInteger {
private static AtomicInteger b = new AtomicInteger();
public static void AtomicInteger() {
Thread[] threads = new Thread[5];
//定义5个线程池,每个线程增加10
for (int i = 0; i < 5; i++) {
threads[i] = new Thread(() -> {
try {
for (int j = 0; j < 10; j++) {
System.out.println("线程:" + Thread.currentThread().getName()
+ ",结果:" + b.incrementAndGet());
Thread.sleep(500);
}
} catch (Exception e) {
e.printStackTrace();
}
});
threads[i].start();
}
}
}
三、源码分析
方法声明 | 描述 |
---|---|
int accumulateAndGet(int x, IntBinaryOperator accumulatorFunction) | 使用IntBinaryOperator 对当前值和x进行计算, 并更新当前值,返回计算后的新值 |
int addAndGet(int delta) | 以原子方式将给定值与当前值相加,返回相加 后的新值 |
boolean compareAndSet(int expect, int update) | 如果当前值 == expect,则以原子方式将该值设置 为给定的更新值(update) |
int decrementAndGet() | 以原子方式将当前值减 1,返回新值 |
int get() | 获取当前值 |
int getAndAccumulate(int x, IntBinaryOperator accumulatorFunction) | 使用IntBinaryOperator 对当前值和x进行计算, 并更新当前值,返回计算前的旧值 |
int getAndAdd(int delta) | 以原子方式将给定值与当前值相加,返回旧值 |
int getAndDecrement() | 以原子方式将当前值减 1,返回旧值 |
int getAndIncrement() | 以原子方式将当前值加 1,返回旧值 |
int getAndSet(int newValue) | 以原子方式设置为给定值,并返回旧值 |
int getAndUpdate(IntUnaryOperator updateFunction) | 使用IntBinaryOperator 对当前值进行计算, 并更新当前值,返回计算前的旧值 |
int incrementAndGet() | 以原子方式将当前值加 1,返回新值 |
void lazySet(int newValue) | 设置为给定值,但不保证值的改变被其他线程立即看到 |
void set(int newValue) | 设置为给定值 |
int updateAndGet(IntUnaryOperator updateFunction) | 使用IntBinaryOperator 对当前值进行计算, 并更新当前值,返回计算后的新值 |
boolean weakCompareAndSet(int expect, int update) | 无法保证除操作目标外的其他变量的执行顺序(编译器 和处理器为了优化程序性能而对指令序列进行重新排序), 同时也无法保证这些变量的可见性。 |
3.1 属性
ActomicInteger
的内部属性可以看到,它是依赖Unsafe
的一些底层能力,进行底层操作,以volatile
的value
字段,记录数值,以保证可见性。以下是AtomicInteger
的部分源码
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// setup to use Unsafe.compareAndSwapInt for updates
// 获取Unsafe的实例
private static final Unsafe unsafe = Unsafe.getUnsafe();
// 标识value字段的偏移量
private static final long valueOffset;
// 静态代码块,通过unsafe获取value的偏移量
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
// 存储int类型值的地方,使用volatile修饰
private volatile int value;
/**
* 获取value
*
* @return 当前的value
*/
public final int get() {
return value;
}
/**
* 立即修改或者设置value
* set操作能够保证可见性,避免指令重排
*
* @param newValue 设置的新值
*/
public final void set(int newValue) {
value = newValue;
}
//...
}
Unsafe
会利用value
字段的内存地址偏移,直接完成操作。
3.2 构造方法
AtomicInteger
提供了两个构造器,使用默认构造器时,内部int
类型的value
值为0
:
/**
* 创建一个AtomicInteger,初始值value为initialValue
*/
public AtomicInteger(int initialValue) {
value = initialValue;
}
/**
* 创建一个AtomicInteger,初始值value为0
*/
public AtomicInteger() {
}
AtomicInteger
类的内部并不复杂,所有的操作都针对内部的int
值——value
,并通过Unsafe
类来实现线程安全的CAS
操作。
3.3 compareAndSet方法
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
/**
* Unsafe中的方法
* @param o 操作的对象
* @param offset 对象中字段的偏移量
* @param expected 原来的值,即期望的值
* @param x 要修改的值
*/
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
调用Unsafe.compareAndSwapInt()
方法实现,这是一个native
方法,底层是使用C/C++
写的,主要是调用CPU
的CAS
指令来实现,它能够保证只有当对应偏移量处的字段值是期望值时才更新,即类似下面这样的两步操作:
if(value == expect) {
value = newValue;
}
通过CPU
的CAS
指令可以保证这两步操作是一个整体,也就不会出现多线程环境中可能比较的时候value
值是a
,而到真正赋值的时候value
值可能已经变成b
了的问题。
3.4 getAndIncrement()方法
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
/**
* Unsafe中的方法
* @param o 操作的对象
* @param offset 对象中字段的偏移量
* @param delta 要增加的值
*/
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
getAndIncrement()
方法底层是调用的Unsafe
的getAndAddInt()
方法,可以看到它是先获取当前的值,然后再调用compareAndSwapInt()
尝试更新对应偏移量处的值,如果成功了就跳出循环,如果不成功就再重新尝试,直到成功为止,这可不就是(CAS+自旋)的乐观锁机制么
3.5 incrementAndGet方法
上面案例中使用了AtomicInteger
的incrementAndGet
方法,以原子的操作对int
值进行自增,该段程序执行的最终结果为50
(5
个线程,每个线程对AtomicInteger
增加10
),如果不使用AtomicInteger
,使用原始的int
或Integer
,最终结果值可能会小于50
(并发时读到了过时的数据或存在值覆盖的问题)。
我们来看下incrementAndGet内部:
/**
* 将value加1,并返回新值,原子操作
*
* @return value加1后的值
*/
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
内部调用了Unsafe
类的getAndAddInt
方法,以原子方式将value
值增加1
,然后返回增加前的原始值。
注意,上述是JDK1.8的实现,在JDK1.8之前,上述方法采用了自旋+CAS操作的方式:
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
3.6 getAndIncrement方法
/**
* 将value加1,然后返回旧值,原子操作
*
* @return 旧值
*/
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
getAndIncrement
是需要明确返回值的,因此getAndAddInt
实现是需要失败重试,最后拿到返回值的。
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
CompareAndset
这样的直接返回Boolean
值,不需要失败重试。
3.7 lazySet方法
AtomicInteger
中有一个比较特殊的方法——lazySet
:lazySet
方法是set
方法的不可见版本。什么意思呢?
/**
* 不会立即修改或者设置值(但是最终会)
* lazySet不能保证可见性,可能会发生指令重排,但是性能比set高
*
* @param newValue 要设置的值
* @since 1.6
*/
public final void lazySet(int newValue) {
unsafe.putOrderedInt(this, valueOffset, newValue);
}
我们知道通过volatile
修饰的变量,可以保证在多处理器环境下的“可见性”。也就是说当一个线程修改一个共享变量时,其它线程能立即读到这个修改的值。volatile
的实现最终是加了内存屏障:
- 保证写
volatile
变量会强制把CPU
写缓存区的数据刷新到内存 - 读
volatile
变量时,使缓存失效,强制从内存中读取最新的值 - 由于内存屏障的存在,
volatile
变量还能阻止重排序
lazySet
内部调用了Unsafe
类的putOrderedInt
方法,通过该方法对共享变量值的改变,不一定能被其他线程立即看到。也就是说以普通变量的操作方式来写变量。
为什么会有这种奇怪方法?什么情况下需要使用lazySet
呢?
考虑下面这样一个场景:
private AtomicInteger ai = new AtomicInteger();
lock.lock();
try {
// ai.set(1);
} finally {
lock.unlock();
}
由于锁的存在:
- lock() 方法获取锁时,和
volatile
变量的读操作一样,会强制使CPU
缓存失效,强制从内存读取变量。 - unlock() 方法释放锁时,和
volatile
变量的写操作一样,会强制刷新CPU
写缓冲区,把缓存数据写到主内存
所以,上述ai.set(1)
可以用ai.lazySet(1)
方法替换:由锁来保证共享变量的可见性,以设置普通变量的方式来修改共享变量,减少不必要的内存屏障,从而提高程序执行的效率。
3.8 其他方法
AtomicInteger
中的其它方法几乎都是类似的,最终会调用到Unsafe
的compareAndSwapInt()
来保证对value
值更新的原子性。
/**
* 设置新值,并且返回旧值(原子操作)
*
* @param newValue 新值
* @return 旧值
*/
public final int getAndSet(int newValue) {
return unsafe.getAndSetInt(this, valueOffset, newValue);
}
/**
* 原子操作,CAS,当value和expect相等时,才将value修改为update
*
* @param expect 期望value的值
* @param update 要修改的值
* @return true:value和expect相等,且完成修改;false:value和expect不相等
*/
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
/**
* 和compareAndSet相同功能,都是使用CAS原子操作,但是无法保证多个线程CAS的有序性
*
* @param expect 期望的value值
* @param update 更改后的值
* @return 操作是否成功
*/
public final boolean weakCompareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
/**
* 将value减1,然后返回旧值,原子操作
*
* @return 旧值
*/
public final int getAndDecrement() {
return unsafe.getAndAddInt(this, valueOffset, -1);
}
/**
* 对value增加指定值,然后返回旧值,原子操作
*
* @param delta 要加的值
* @return 旧值
*/
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
/**
* 将value减1,并返回新值,原子操作
*
* @return value减1后的新值
*/
public final int decrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
}
/**
* 对value增加值,然后返回新值,原子操作
*
* @param delta 新增的值
* @return value新增后的值
*/
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
/**
* 传入Function,对value进行操作(同样使用CAS保证原子性),会一直重试直到成功才中断,然后返回旧值
*
* @param updateFunction a side-effect-free function
* @return 旧值
* @since 1.8
*/
public final int getAndUpdate(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return prev;
}
/**
* 传入Function,对value进行操作(同样使用CAS保证原子性),会一直重试直到成功才中断,然后返回新值
*
* @param updateFunction a side-effect-free function
* @return the updated value
* @since 1.8
*/
public final int updateAndGet(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
// 一直重试,直到CAS操作成功
} while (!compareAndSet(prev, next));
return next;
}
/**
* 阻塞式更新,并对prev和x,进行二元运算操作
*
* @param x the update value
* @param accumulatorFunction a side-effect-free function of two arguments
* @return the previous value
* @since 1.8
*/
public final int getAndAccumulate(int x, IntBinaryOperator accumulatorFunction) {
int prev, next;
do {
prev = get();
next = accumulatorFunction.applyAsInt(prev, x);
} while (!compareAndSet(prev, next));
return prev;
}
/**
* Atomically updates the current value with the results of
* applying the given function to the current and given values,
* returning the updated value. The function should be
* side-effect-free, since it may be re-applied when attempted
* updates fail due to contention among threads. The function
* is applied with the current value as its first argument,
* and the given update as the second argument.
*
* @param x the update value
* @param accumulatorFunction a side-effect-free function of two arguments
* @return the updated value
* @since 1.8
*/
public final int accumulateAndGet(int x, IntBinaryOperator accumulatorFunction) {
int prev, next;
do {
prev = get();
next = accumulatorFunction.applyAsInt(prev, x);
} while (!compareAndSet(prev, next));
return next;
}
public String toString() {
return Integer.toString(get());
}
public int intValue() {
return get();
}
public long longValue() {
return (long) get();
}
public float floatValue() {
return (float) get();
}
public double doubleValue() {
return (double) get();
}
四、拓展
4.1 为什么使用CAS而不用Synchronized呢?
synchronized加锁同一时间段只允许一个线程访问,能够保证一致性但是并发性下降。而是用CAS算法使用do-while不断判断而没有加锁(实际是一个自旋锁),保证一致性和并发性。
五、总结
AtomicInteger
中维护了一个使用volatile
修饰的变量value
,保证可见性;AtomicInteger
中的主要方法最终几乎都会调用到Unsafe
的compareAndSwapInt()
方法保证对变量修改的原子性。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器