全称是CompareAndSwap,它是一条CPU并发原语。用来判断内存某个位置的值是否为预期值,如果是则改为更新的值,这个过程是原子的。AtomicInteger之所以能保证原子性是依赖于UnSafe类,这个类是Java最底层的类之一,里面都是很屌的native方法,都是其他语言写的,咱看不见,Unsafe类可以执行以下几种操作:
public final class Unsafe {
//源码确实美如画
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;
}
}
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// setup to use Unsafe.compareAndSwapInt for updates
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;
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final int getAndIncrement() {
//this指的是这个对象本身
return unsafe.getAndAddInt(this, valueOffset, 1);
}
}
CAS算法是取出内存中某时刻的数据并在当下时刻比较并替换,这中间会有一个时间差导致数据变化。比如说有两个线程,一快一慢,同时从主内存中取变量A,快线程将变量改为B并写入主内存,然后又将B从主内存中取出再改为A写入主内存,这时候慢线程刚完成了工作,使用CAS算法,发现预期值还是A,然后慢线程将自己的结果写入主内存。虽然慢线程操作成功,但是这个过程可能是有问题的
AtomicReference用来把特定对象编程原子类,AtomicReference和AtomicInteger非常类似,不同之处就在于AtomicInteger是对整数的封装,底层采用的是compareAndSwapInt实现CAS,比较的是数值是否相等,而AtomicReference则对应普通的对象引用,底层使用的是compareAndSwapObject实现CAS,比较的是两个对象的地址是否相等,也就是它可以保证你在修改对象引用时的线程安全性。
对于ABA问题可以在比较值的同时加上版本号,或者说是时间戳。AtomicStampedReference
这个类在创建的时候要求指定一个初始的版本号,并且每次做CAS操作的时候要求对比版本号,且版本号需要自增
/**
* Creates a new {@code AtomicStampedReference} with the given
* initial values.
*
* @param initialRef the initial reference
* @param initialStamp the initial stamp
*/
public AtomicStampedReference(V initialRef, int initialStamp) {
pair = Pair.of(initialRef, initialStamp);
}
/**
* Atomically sets the value of both the reference and stamp
* to the given update values if the
* current reference is {@code ==} to the expected reference
* and the current stamp is equal to the expected stamp.
*
* @param expectedReference the expected value of the reference
* @param newReference the new value for the reference
* @param expectedStamp the expected value of the stamp
* @param newStamp the new value for the stamp
* @return {@code true} if successful
*/
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)));
}