并发类 AtomicInteger 使用入门及源码详解
前言
在JDK 1.5版本之前,为了确保在多线程下对某基本数据类型或者引用数据类型运算的原子性,必须依赖于关键字synchronized,但是自JDK 1.5版本以后这一情况发生了改变,JDK官方为开发者提供了原子类型的工具集,比如AtomicInteger、AtomicBoolean等,这些原子类型都是Lock-Free及线程安全的,开发者将不再为一个数据类型的自增运算而增加synchronized的同步操作。
AtomicInteger的基本用法
与int的引用类型Integer继承Number类一样,AtomicInteger也是Number类的一个子类,除此之外,AtomicInteger还提供了很多原子性的操作方法,本节将为大家逐一介绍。在AtomicInteger的内部有一个被volatile关键字修饰的成员变量value,实际上,AtomicInteger所提供的所有方法主要都是针对该变量value进行的操作。
1、AtomicInteger的创建
public AtomicInteger():创建AtomicInteger的初始值为0。
public AtomicInteger(int initialValue):创建AtomicInteger并且指定初始值,无参的AtomicInteger对象创建等价于AtomicInteger(0)。
2、AtomicInteger的Incremental操作
x++或者x=x+1这样的操作是非原子性的,要想使其具备原子性的特性,我们可以借助AtomicInteger中提供的原子性Incremental的操作方法。
int getAndIncrement():
返回当前int类型的value值,然后对value进行自增运算,该操作方法能够确保对value的原子性增量操作。
public static void main(String[] args){
final AtomicInteger ai = new AtomicInteger(5);
// 返回AtomicInteger的int值,然后自增(在多线程的情况下,下面的断言未必正确)
boolean a = ai.getAndIncrement() == 5;
// 获取自增后的结果(在多线程的情况下,下面的断言未必正确)
boolean b = ai.get() == 6;
}int incrementAndGet():
直接返回自增后的结果,该操作方法能够确保对value的原子性增量操作。
public static void main(String[] args) { // 定义AtomicInteger,初值为5
final AtomicInteger ai = new AtomicInteger(5);
// 返回value自增后的结果
assert ai.incrementAndGet() == 6;
assert ai.get() == 6;
}3、AtomicInteger的Decremental操作
x--或者x=x-1这样的自减操作同样也是非原子性的,要想使其具备原子性的特性,我们可以借助AtomicInteger中提供的原子性Decremental的操作方法。
int getAndDecrement():
返回当前int类型的value值,然后对value进行自减运算,该操作方法能够确保对value的原子性减量操作
AtomicInteger ai = new AtomicInteger(5);
assert ai.getAndDecrement() == 5;
assert ai.get() == 4;int decrementAndGet():
直接返回自减后的结果,该操作方法能够确保对value的原子性减量操作。
AtomicInteger ai = new AtomicInteger(5);
assert ai.decrementAndGet() == 4;
assert ai.get() == 4;4、原子性地更新value值
boolean compareAndSet(int expect, int update):
原子性地更新AtomicInteger的值,其中expect代表当前的AtomicInteger数值,update则是需要设置的新值,该方法会返回一个boolean的结果:当expect和AtomicInteger的当前值不相等时,修改会失败,返回值为false;若修改成功则会返回true。
// 定义一个AtomicInteger类型的对象ai并且指定初值为10
AtomicInteger ai = new AtomicInteger(10);
// 调用compareAndSet方法,expect的值为100,修改肯定会失败
assert !ai.compareAndSet(100, 12);
// 修改并未成功,因此新值不等于12
assert ai.get() != 12;
// 执行了compareAndSet更新方法之后,ai的返回值依然为10,因为修改失败
assert ai.get() == 10;
// 调用compareAndSet方法,expect的值为10,修改成功(多线程情况下并不能担保百分之百成功,
// 关于这一点,在2.1.3节中会为大家讲解)
assert ai.compareAndSet(10, 12);
// 断言成功
assert ai.get() == 12;boolean weakCompareAndSet(int expect, int update):
目前版本JDK中的该方法与compareAndSet完全一样,源码如下所示。
通过源码我们不难发现两个方法的实现完全一样,那么为什么要有这两个方法呢?其实在JDK 1.6版本以前双方的实现是存在差异的,compareAndSet方法的底层主要是针对Intel x86架构下的CPU指令CAS:cmpxchg(sparc-TSO,ia64的CPU架构也支持),但是ARM CPU架构下的类似指令为LL/SC:ldrex/strex(ARM架构下的CPU主要应用于当下的移动互联网设备,比如在智能手机终端设备中,高通骁龙、华为麒麟等系列都是基于ARM架构和指令集下的CPU产品),或许在运行Android的JVM设备上这两个方法底层存在着差异。
int getAndAdd(int delta):
原子性地更新AtomicInteger 的value值,更新后的value为value和delta之和,方法的返回值为value的前一个值,该方法实际上是基于自旋+CAS算法实现的(Compare And Swap)原子性操作。
// 定义一个AtomicInteger类型的对象ai并且指定初始值为10
AtomicInteger ai = new AtomicInteger(10);
// 调用getAndAdd方法,返回value的前一个值为10
assert ai.getAndAdd(2) == 10;
// 调用get方法返回AtomicInteger的value值,当前返回值为12
assert ai.get() == 12;int addAndGet(int delta):
该方法与getAndAdd(int delta) 一样,也是原子性地更新AtomicInteger的value值,更新后的结果value为value和delta之和,但是该方法会立即返回更新后的value值。
// 定义一个AtomicInteger类型的对象ai并且指定初始值为10
AtomicInteger ai = new AtomicInteger(10);
// 调用addAndGet方法,返回当前value的值
assert ai.addAndGet(2)==12;
// 调用get方法返回AtomicInteger的value值,当前返回值为12
assert ai.get() == 12;5、AtomicInteger与函数式接口
自JDK1.8增加了函数式接口之后,AtomicInteger也提供了对函数式接口的支持。
int getAndUpdate(IntUnaryOperator updateFunction):
原子性地更新AtomicInteger的值,方法入参为IntUnaryOperator接口,返回值为value更新之前的值。
@FunctionalInterfacepublic interface IntUnaryOperator {
// 入参为被操作数,对应于AtomicInteger的当前value值
int applyAsInt(int operand);
}
//IntUnaryOperator为函数式接口,有且仅有一个接口方法(非静态,非default),
//接口方法的返回值即AtomicInteger被更新后的value的最新值。// 定义一个AtomicInteger类型的对象ai并且指定初始值为10
AtomicInteger ai = new AtomicInteger(10);
// 调用getAndUpdate方法并且传入lambda表达式,返回结果为value的前一个值
assert ai.getAndUpdate(x -> x + 2) == 10;
// 调用get方法返回AtomicInteger的value值,当前返回值为12
assert ai.get() == 12;int updateAndGet(IntUnaryOperator updateFunction):
原子性地更新AtomicInteger的值,方法入参为IntUnaryOperator接口,该方法会立即返回更新后的value值。
// 定义一个AtomicInteger类型的对象ai并且指定初始值为10
AtomicInteger ai = new AtomicInteger(10);
// 调用updateAndGet方法并且传入lambda表达式,返回结果为value更新后的值
assert ai.updateAndGet(x -> x + 2) == 12;
// 调用get方法返回AtomicInteger的value值,当前返回值为12
assert ai.get() == 12;int getAndAccumulate(int x, IntBinaryOperator accumulatorFunction):
原子性地更新AtomicInteger的值,方法入参为IntBinaryOperator接口和delta值x,返回值为value更新之前的值。
@FunctionalInterfacepublic interface IntBinaryOperator {
// 该接口在getAndAccumulate方法中,left为AtomicInteger value的当前值,
// right为delta值,返回值将被用于更新AtomicInteger的value值
int applyAsInt(int left, int right);
}
//IntBinaryOperator为函数式接口,有且仅有一个接口方法(非静态,非default),
//接口方法的返回值即AtomicInteger被更新后的value的最新值。// 定义一个AtomicInteger类型的对象ai并且指定初值为10
AtomicInteger ai = new AtomicInteger(10);
int result = ai.getAndAccumulate(5, Integer::sum);
assert result == 10;
assert ai.get() == 15;int accumulateAndGet(int x, IntBinaryOperator accumulatorFunction):
该方法与getAndAccumulate类似,只不过会立即返回AtomicInteger的更新值。
// 定义一个AtomicInteger类型的对象ai并且指定初值为10
AtomicInteger ai = new AtomicInteger(10);
int result = ai.accumulateAndGet(5, Integer::sum);
assert result == 15;
assert ai.get() == 15;6、其他方法
void set(int newValue):
为AtomicInteger的value设置一个新值,通过对前面内容的学习,我们知道在AtomicInteger中有一个被volatile关键字修饰的value成员属性,因此调用set方法为value设置新值后其他线程就会立即看见。
void lazySet(int newValue):
set方法修改被volatile关键字修饰的value值会被强制刷新到主内存中,从而立即被其他线程看到,这一切都应该归功于volatile关键字底层的内存屏障。内存屏障虽然足够轻量,但是毕竟还是会带来性能上的开销,例如,在单线程中对AtomicInteger的value进行修改时没有必要保留内存屏障,而value又是被volatile关键字修饰的,这似乎是无法调和的矛盾。lazySet方法的作用正在于此。
int get():
返回AtomicInteger的value当前值。
AtomicInteger分析
下面AtomicInteger类的内部原理。
// Unsafe是由C++实现的,其内部存在着大量的汇编 CPU指令等代码,JDK实现的
// Lock Free几乎完全依赖于该类
private static final Unsafe unsafe = Unsafe.getUnsafe();
// valueOffset将用于存放value的内存地址偏移量
private static final long valueOffset;
static {
try {
// 获取value的内存地址偏移量 v
alueOffset =
unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) {
throw new Error(ex);
}
}
// 我们不止一次地说过,在AtomicInteger的内部有一个volatile修饰的int类型成员属性value
private volatile int value;1. CAS算法
CAS包含3个操作数:内存值V、旧的预期值A、要修改的新值B。当且仅当预期值A与内存值V相等时,将内存值V修改为B。
compareAndSwapInt方法是一个native方法,提供了CAS(Compare And Swap)算法的实现,AtomicInteger类中的原子性方法几乎都借助于该方法实现。
public final boolean weakCompareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
// Unsafe 内部方法getAndAddInt源码
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while (!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}进入Unsafe源码中我们会看到compareAndSwapInt 源码。
通过对compareAndSwapInt 方法的简单分析,既然可以通过AtomicInteger获得当前值,那么为什么还会出现expectValue和AtomicInteger当前值不相等的情况呢?比如下面的代码片段。
AtomicInteger ai = new AtomicInteger(2);ai.compareAndSet(ai.get(),10);
原因是相对于synchronized关键字、显式锁Lock,AtomicInteger所提供的方法不具备排他性,当A线程通过get()方法获取了AtomicInteger value的当前值后,B线程对value的修改已经顺利完成;A线程试图再次修改的时候就会出现expectValue与value的当前值不相等的情况,因此会出现修改失败,这种方式也被称为乐观锁。对数据进行修改的时候,首先需要进行比较。
2. 自旋方法addAndGet源码分析
由于compareAndSwapInt 方法的乐观锁特性,会存在对value修改失败的情况,但是有些时候对value的更新必须要成功,比如调用incrementAndGet、addAndGet 等方法,本节就来分析一下addAndGet 方法的实现。
public final int addAndGet(int delta) {
// 调用Unsafe的getAndAddInt方法
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
// Unsafe类中的getAndAddInt方法
public final int getAndAddInt(Object object, long valueOffset, int delta) {
int currentValue;
do {
// ①
currentValue = this.getIntVolatile(object, valueOffset);
// ②
} while (
!this.compareAndSwapInt(
object,
valueOffset,
currentValue,
currentValue + delta
)
);
return currentValue;
}在getAndAddInt 方法中有一个直到型do..while循环控制语句,首先在注释①处获取当前被volatile关键字修饰的value值(通过内存偏移量的方式读取内存)。
在注释②处执行compareAndSwapInt 方法,如果执行成功则直接返回,如果执行失败则再次执行下一轮的compareAndSwapInt 方法。
通过上面源码的分析,incrementAndGet 的执行结果有可能是11也有可能是比11更大的值。
AtomicInteger ai = new AtomicInteger(10);
//这句断言在多线程的情况下未必会成功
assert ai.incrementAndGet() == 11;自旋方法addAndGet的执行步骤如图1所示。
