Atomic原子操作类是JUC包下的类,包含AtomicInteger、AtomicBoolean、AtomicLong、AtomicReference等等,AtomicReference是可以自定定义类型的原子操作类。原理都是使用volatile和CAS(比较并交换)完成操作。
volatile是java关键字,轻量级的同步机制,用来保证变量的可见性,用于多线程。线程的操作是在线程私有的工作内存中,而变量的存储是在主内存中,所以线程修改变量时先把变量copy到工作内存中,操作完成后回写到主内存。volatile保证了变量可见性,当一个线程修改变量后,会将结果写到主内存中,并通知其他线程变量已被修改,需要重新从主内存中获取。
class MyCount {
int n;
AtomicInteger ai = new AtomicInteger(0);
}
public class AtomicTest {
public static void main(String[] args) {
MyCount myCount = new MyCount();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
for (int j = 0; j < 500; j++) {
myCount.n++;
myCount.ai.getAndIncrement(); // 对应n++
}
}).start();
}
// 等待线程执行完成
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("n=" + myCount.n);
System.out.println("ai=" + myCount.ai);
}
}
运行结果
n=14869
ai=15000
由于n++不是原子操作(要么都执行,要么都不执行),分为3步:1 获取n值;2 值+1;3 结果赋值给n。如果两个线程同时执行,线程1取到n为1,并且准备将2赋值给n时,线程1被挂起,此时线程2获取n也为1,执行完成后将n改为2,线程1继续执行,将线程2的结果覆盖。导致最后执行结果不是15000。
而AtomicInteger.getAndIncrement()属于原子操作,运行结果为15000。我们来看一下AtomicInteger类的源码:
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset; // 可以理解为对象在内存中的偏移量
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
public AtomicInteger(int initialValue) {
value = initialValue;
}
public AtomicInteger() {
}
...
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
...
}
AtomicInteger提供了很多操作方法,本质都是调用了Unsafe类的方法,追踪一下Unsafe类:
public final class Unsafe {
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
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在执行getAndAddInt方法时,使用了do while循环,相当于自旋。首先从主内存中获取最新的值,然后通过CAS比较刚获取的结果,如果一致则改为新值, 如果不一致,重新从内存中取并再次比较交换,直至成功。
Unsafe类是sun.misc包下的类,存在于JDK中lib目录下的rt.jar包中,内部封装了很多CAS方法,属于本地方法,是通过汇编语言操作CPU指令,防止执行被加塞的原子操作。
CAS类似于MySQL数据操作中的乐观锁,更新时比较旧值。CAS有以下缺点:
1. 空循环,do while失败一致循环操作
2. 只能对一个变量进行CAS
3. ABA问题(重点)
ABA问题:当操作数同时被多个线程做加和减时,线程1取到值后,改为2,线程2取到后改回了1,线程3取到1为1,然后改为2,线程3对线程1和线程2的操作无感知。解决此问题可以使用AtomicStampedReference类进行操作。
AtomicStampedReference原理就是用两个CAS进行判定操作,一个对变量操作, 另一个是stamp,应该每次操作固定加或者减,如果变量被ABA操作,但是stamp值已经发生改变,CAS会返回失败。
class MyCount {
AtomicInteger ai = new AtomicInteger(0);
AtomicStampedReference<Integer> asr = new AtomicStampedReference<>(0, 0);
}
public class AtomicTest {
public static void main(String[] args) {
MyCount myCount = new MyCount();
int stamp = myCount.asr.getStamp();
new Thread(() -> {
myCount.ai.compareAndSet(0, 1);
myCount.ai.compareAndSet(1, 0);
System.out.println(Thread.currentThread().getName() + "执行后 ai=" + myCount.ai);
myCount.asr.compareAndSet(0, 1, stamp, stamp + 1);
myCount.asr.compareAndSet(1, 0, myCount.asr.getStamp(), myCount.asr.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "执行后 asr=" + myCount.asr.getReference());
}, "Thread1").start();
new Thread(() -> {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
myCount.ai.compareAndSet(0, 1);
System.out.println(Thread.currentThread().getName() + "执行后 ai=" + myCount.ai);
boolean result = myCount.asr.compareAndSet(0, 1, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName() + "执行" + result + " asr=" + myCount.asr.getReference());
}, "Thread2").start();
}
}
运行结果
Thread1执行后 ai=0
Thread1执行后 asr=0
Thread2执行后 ai=1
Thread2执行false asr=0
在CAS方法内,判断原reference、stamp是否和当前值一致, 如果不一致返回false;如果一致,判断新的reference、stamp未发生改变,返回成功, 如果发生了改变,使用Unsafe的本地方法调用CAS交换对象,返回结果
public class AtomicStampedReference<V> {
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);
}
}
private volatile Pair<V> pair;
...
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
...
private boolean casPair(Pair<V> cmp, Pair<V> val) {
return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}
...
}