java-juc-原子类-AtomicInteger初探

研究原子类型,我们先从最常用的AtomicInteger开始看起。

我们可以先看看AtomicInteger的使用方法:

1
2
3
4
AtomicInteger i = new AtomicInteger(0);
i.addAndGet(10); // print 10
i.getAndAdd(10); // print 10
i.get(); // 20

用法还是很简单的,我们也可以看到AtomicInteger等一系列原子数据类是为了解决多线程访问Integer变量导致可能不出现的结果所设计实现的一个基于原子操作的Integer类。

那么老规矩,研究源码, 基于jdk 1.8.0_05 。

看看构造方法。

1
2
3
4
5
6
7
8
private volatile int value;

public AtomicInteger(int initialValue) {
value = initialValue;
}

public AtomicInteger() {
}

可以看到实际上AtomicInteger用一个volatile int保存了当前要进行一系列操作的int对象。当然也有一个无参数的构造方法,可以通过set()方法给value赋值,实现的功能是一样的。

对于这个value的操作就涉及到了一个Unsafe类。

1
private static final Unsafe unsafe = Unsafe.getUnsafe();

Unsafe类主要是用来实现一个不安全的操作的比较特殊的类,Unsafe能够提供很多保证安全的方法,在这里主要是提供了CAS操作。

CAS对int类型的数据进行操作时,主要使用了这个方法:

1
public final native boolean compareAndSwapInt(Object object, long valueOffset, int expect, int update);

而这里的valueOffset是一个全局的final long 数据,表示一个内存位置。

1
2
3
4
5
6
7
8
private static final long valueOffset;

static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}

这里的value就是之前提到的volatile int 的值value,这里主要是为了保证这个value的可见性,某个线程修改了value后,其他线程也能读取到正确的值。

另外,通过unsafe提供的CAS方法,可以在AtomicInteger中修改值。

1
2
3
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

所以我们可以看到通过CAS来保证原子性,通过volatile保证了可见性。

阅读源码发现,AtomicInteger还有几个很有意思的方法。也是我们一开始就见到的常用用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}

public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}

/*这段代码位于Unsafe*/
public final int getAndAddInt(Object object, long valueOffset, int delta) {
int ans;
do {
ans = this.getIntVolatile(object, valueOffset);
} while(!this.compareAndSwapInt(object, valueOffset, ans, ans + delta));

return ans;
}

用法我在一开始也通过例子解释了,getAndAdd是先通过unsafe.getAndAddInt获取当前的安全值然后再原子操作加上传进来的参数delta,而addAndGet是同样的unsafe.getAndAddInt获取当前的
安全值,并且原子操作加上delta。这里使用的同样的原子操作,但是有一个trick是同样的原子操作进行先取值再加值,addAndGet是返回了取出的值直接加上delta。这里真的很巧妙啊。为什么我要说这里很有意思呢? 我们可以看看在jdk 1.7的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public final int getAndAdd(int delta) {
for (;;) {
int current = get();
int next = current + delta;
if (compareAndSet(current, next))
return current;
}
}

public final int addAndGet(int delta) {
for (;;) {
int current = get();
int next = current + delta;
if (compareAndSet(current, next))
return next;
}
}

public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

可以看到,1.8到1.7,将AtomicInteger中暴露的Unsafe的方法使用的更少了。代码看着更简洁了。
并且for循环也换成了do{}while(), 虽然性能没什么改变,但是更直观了。

好,回到原子性上来。既然提到了CAS,我们也看到了Unsafe中的CAS的方法使用,主要在native层使用原子指令实现对值的修改,需四个参数,其中valueOffSet是内存地址,expect是旧的期望正确的值,update是新的值。

只有旧值匹配时,才会将旧值更新为新值。关于CAS的具体学习,我们以后再继续深入,今天的主角是AtomicInteger。

除了getAndAdd,还有getAndIncrement等方法,其实本质都是一样的,只不过delta确认为1而已。

另外,相比较jdk 1.7, 在jdk1.8中新拓展了两个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public final int getAndUpdate(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return prev;
}

public final int updateAndGet(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return next;
}

简单用法如下:

1
2
AtomicInteger ai = new AtomicInteger(10);
ai.updateAndGet(n -> (n %2 == 0 ? n-2:n -1));

IntUnaryOperator是一个jdk 1.8的新增类,主要完成对单一值运算操作得到一个新结果的过程。例如上面的例子中就是将偶数减2,奇数减1的一个“side-effect-free” 函数,也就是对单一值进行运算。

除了以上对各种值的原子性修改以外,AtomicInteger还提供了一些方法将int转为其他类型。

1
2
3
4
5
6
7
8
9
10
11
public long longValue() {
return (long)get();
}

public float floatValue() {
return (float)get();
}

public double doubleValue() {
return (double)get();
}

好了,关于AtomicInteger的学习研究先到这儿吧,主要需要弄懂Unsafe在其中起的作用以及jdk 1.8的一些新变化。其实其他几个原子基本数据类的方法和实现与AtomicInteger差不多,就不一一介绍了,搞懂这一个,其他就触类旁通了。