原子类 AtomicInteger
前言
当多线程更新某个共享变量时,可能会出现非预期结果,比如i++这样的操作,解决的办法有使用synchronized 或者 Lock 锁,但这样做其实让多线程不会同时更新这个变量,虽然线程安全了,但存在效率、性能问题。
jdk1.5提供了一系列原子操作类,它们存在于java.util.concurrent.atomic包下,这些原子类具有简单、性能高效、线程安全的优点。
代码示例
1 | public class IntTest implements Runnable { |
预期打印1000,但多次运行,总得到小于1000的值,这是因为成员变量num ,存在线程安全问题。
修改为用 AtomicInteger:
1 | public class AtomicIntegerTest implements Runnable { |
反复运行得到的结果都是1000,保证了原子性。
AtomicInteger 常用方法
int get()
获取 AtomicInteger 的值value
void lazySet(int newValue)
最终会设置成newValue,但是可能导致其他线程在一段时间内仍可以读到旧的值。
int getAndSet(int newValue)
以原子的方式设置为newValue,并返回旧值
boolean compareAndSet(int expect, int update)
如果输入的值等于预期值,则以原子的方式更新为新的值update
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
int getAndIncrement()
返回旧值,自增1
int getAndDecrement()
返回旧值,自减1
int getAndAdd(int delta)
返回旧值,自增步长 delta
int incrementAndGet()
自增1,返回新的值
AtomicInteger 原理
1 |
|
属性 value 是 AtomicInteger 包装类真正的值,在初始化时可以指定其初始值,默认为0;
value用 volatile 修饰,我们知道 volatile可以保证可见性,但不能保证原子性,因为i++这种不是原子操作。
以方法getAndIncrement()
为例:
1 | public final int getAndIncrement() { |
查看unsafe.getAndAddInt(…)源码:
1 | public final int getAndAddInt(Object var1, long var2, int var4) { |
getIntVolatile(var1, var2)
和compareAndSwapInt(var1, var2, var5, var5 + var4)
是 native 方法,调用 C 语言。
在getAndAddInt(...)
方法中,通过不断自旋CAS 的方式去更新。
AtomicInteger的其他原子更新方法也都是基于此。
自旋+CAS 的缺点
并发情况下,对于更新变量,竞争锁的线程较多的时候,每个线程都要去进行自旋,很消耗 CPU。
LongAdder
jkd8引入 LongAdder,在线程较多的并发情况下,它的性能要比Atomic*使用自旋+CAS 的方式要高,其核心原理是在自旋+CAS 的基础上,引用了锁分段技术。
LongAdder 比 AtomicLong 更高效,缺点是如果有并发更新,可能导致最终的数据有误差。
更适合在高并发下、没有并发更新时进行统计的需求。
- Title: 原子类 AtomicInteger
- Author: 薛定谔的汪
- Created at : 2018-08-23 18:01:54
- Updated at : 2023-11-17 19:37:37
- Link: https://www.zhengyk.cn/2018/08/23/java/AtomicInteger/
- License: This work is licensed under CC BY-NC-SA 4.0.