概述
jdk1.5后提供了一些原子操作的类,应用开发者使用起来,更加简单、高效、更加安全的进行并发更新变量。这些原子类都在java.util.concurrent.atomic包下。总体来说,有五类原子类。
- 原子更新基本类型类:AtomicInteger、AtomicBoolean、AtomicLong、
- 原子更新数组:AtoicIntegerArray、AtomicLongArray、AtomicReferenceArray
- 原子更新引用:AtomicReference、AtomicMarkableReference、AtomicStampedReference
- 原子更新字段类:AtomicLongFieldUpdater
- Java8新增类:LongAdder、DoubleAdder等(这些也属于原子更新基本类型,不过Java8对高并发高竞争场景中的操作,在这些类中做了一些优化,使之更高效,单独拿了出来)
使用例子更多测试例子在github中
public class AtomicIntegerTest {
private static int normalInteger = 0;
private static AtomicInteger atomicInteger = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new AddInteger());
Thread t2 = new Thread(new AddInteger());
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("普通Integer对象多线程++操作结果:" + normalInteger);
System.out.println("AtomicInteger对象多线程累加操作结果:" + atomicInteger.intValue());
}
static class AddInteger implements Runnable {
@Override
public void run() {
//每个线程执行一万次累加操作
for(int i=0; i<10000; i++) {
normalInteger++;
atomicInteger.getAndIncrement();
}
}
}
}
运行结果:
普通Integer对象多线程++操作结果:16176
AtomicInteger对象多线程累加操作结果:20000
结果可以看到,普通的Integer在多线程中共享,i++操作会使结果错误。而AtomicInteger运算正常。
AtomicInteger源码
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// 赋值unsafe对象
private static final Unsafe unsafe = Unsafe.getUnsafe();
//value字段相对于对象的偏移量
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
//使用volatile修饰,保证内存可见性和指令重排
private volatile int value;
//由于value是volatile的,直接返回可以保证是最新值
public final int get() {
return value;
}
//AtomicInteger最常用的一个方法,返回当前值并将当前值进行加1
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
...
}
AtomicInteger使用了volatile保证内存的可见性和unsafe的CAS操作。 上面的注释已经比较清楚了,我们主要看下getAndIncrement方法,该方法调用了unsafe.getAndAddInt方法
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
//使用Volatile语义的获取当前Int值
var5 = this.getIntVolatile(var1, var2);
//更新成功则返回,否则自旋进行下一次取值,更新
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
由于自旋CAS操作有两个缺点。
- ABA问题:即由于多线程共享数据原因,某个数据被其他线程从A更新到B,又从B更新到A,如果程序在意这个变化过程,那么可以使用版本号解决,每次更新进行版本号加1操作。AtomicStampedReference就提供了这个功能。
- CAS自旋消耗CPU资源:Jdk1.8增加了LongAdder等类优化,当竞争非常激烈时,LongAdder等类性能明显好于AtomicLong。
AtomicStampedReference源码
public class AtomicStampedReference<V> {
//将目标对应的引用和版本号stamp构造成一个Pair对象
private static class Pair<T> {
final T reference;
final int stamp;
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
static <T> Pair<T> of(T reference, int stamp) {
return new Pair<T>(reference, stamp);
}
}
//使用volatile 修饰pair对象
private volatile Pair<V> pair;
//AtomicStampedReference构造函数
public AtomicStampedReference(V initialRef, int initialStamp) {
pair = Pair.of(initialRef, initialStamp);
}
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
//expectedReference和pair.reference相等 && expectedStamp和pair.stamp相等时CAS操作
//需要注意的newReference和newStamp也与pair进行了比较,即如果期望值和版本号都没变,直接返回更新成功,不再进行CAS替换
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
。。。
}