含義
CAS(CompareAndSwap) 即比較並替換,實現併發算法時常用到的一種技術。CAS操作包含三個操作數——內存位置、預期原值及新值。執行CAS操作的時候,將內存位置的值與預期原值比較,如果相匹配,那麼處理器會自動將該位置值更新爲新值,否則,處理器不做任何操作。
樂觀鎖的含義就是假設沒有發生衝突,那麼我正好可以進行某項操作,如果要是發生衝突呢,那我就重試直到成功,樂觀鎖最常見的就是CAS。
無論是ReenterLock內部的AQS,還是各種Atomic開頭的原子類,內部都應用到了CAS
原理分析
以java.util.concurrent.atomic.***AtomicInteger***爲例進行分析
/**
* An {@code int} value that may be updated atomically. See the
* {@link java.util.concurrent.atomic} package specification for
* description of the properties of atomic variables. An
* {@code AtomicInteger} is used in applications such as atomically
* incremented counters, and cannot be used as a replacement for an
* {@link java.lang.Integer}. However, this class does extend
* {@code Number} to allow uniform access by tools and utilities that
* deal with numerically-based classes.
*
* @since 1.5
* @author Doug Lea
*/
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 updated value
*/
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
...
}
- Unsafe是位於sun.misc包下的一個類,Unsafe類使Java語言擁有了類似C語言指針一樣操作內存空間的能力,這無疑也增加了程序發生相關指針問題的風險。在程序中過度、不正確使用Unsafe類會使得程序出錯的概率變大,使得Java這種安全的語言變得不再“安全”,因此對Unsafe的使用一定要慎重。
在jdk1.9中,對Usafe進行了刪除,因此那些基於Usafe開發的框架慢慢的都死掉了。
- valueOffset是long類型的,此處代表的含義就是對象成員變量value相對對象內存地址的偏移量
valueOffset的賦值是放在static代碼塊中,也就是說類加載初始化的時候就執行一次,從而獲取了value成員變量相對於當前對象內存地址的偏移量
- value變量使用volatile修飾保證了內存可見性,爲後面的CAS提供了可能性。其實實際存儲的值是放在value中的
- incrementAndGet方法剖析
調用鏈:
incrementAndGet() → unsafe.getAndAddInt(this, valueOffset, 1) + 1 → getAndAddInt(Object var1, long var2, int var4)
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;
}
var1: 對應當前對象this; var2對應偏移量valueOffset;var4對應增量 1;
所以上面代碼等價於
public final int getAndAddInt(Object this,long valueOffset,int 1){
int var5;
do {
var5 = this.getIntVolatile(this, valueOffset);
} while(!this.compareAndSwapInt(this, valueOffset, var5, var5 + 1));
return var5;
}
通過上面的變量替換,只剩下var5了。
var5 = this.getIntVolatile(this, valueOffset);
這是個native方法,其實就是獲取var1中,var2偏移量處的值。var1就是AtomicInteger,var2就是我們前面提到的valueOffset,這樣我們就從內存裏獲取到現在valueOffset處的值了,也就是獲取到當前value的值。
public final int getAndAddInt(Object this,long valueOffset,int 1){
int expect;
do {
expect = this.getIntVolatile(this, valueOffset);
} while(!this.compareAndSwapInt(this, valueOffset, expect, update));
return expect;
}
getAndAddInt方法執行邏輯:
- 通過getIntVolatile(this,valueOffset)獲取到當前value的值,作爲expect(期望值)。
- compareAndSwapInt方法含義:
如果this內的value和expect相等,就證明沒有其他線程改變過這個變量,那麼就更新它爲update,如果這一步的CAS沒有成功,那就採用自旋的方式(無限循環)繼續進行CAS操作
CPU原語
CAS,比較並交換。乍一看這也是兩個步驟了啊,其實在JNI裏是藉助於一個CPU指令(cmpxchgl)完成的。所以還是原子操作。
CAS缺陷
ABA問題
- 問題描述
CAS需要在操作值的時候檢查下值有沒有發生變化,如果沒有發生變化則更新,但是如果一個值原來是A,變成了B,又變成了A,那麼使用CAS進行檢查時會發現它的值沒有發生變化,但是實際上卻變化了。這就是CAS的ABA問題。
- 解決辦法
常見的解決思路是使用版本號。在變量前面追加上版本號,每次變量更新的時候把版本號加一,那麼A-B-A 就會變成1A-2B-3A。
目前在JDK的atomic包裏提供了一個類AtomicStampedReference來解決ABA問題。這個類的compareAndSet方法作用是首先檢查當前引用是否等於預期引用,並且當前標誌是否等於預期標誌,如果全部相等,則以原子方式將該引用和該標誌的值設置爲給定的更新值。
- AtomicStampedReference原理解析
AtomicStampedReference就是加了版本號的AtomicReference。
可以看到,除了傳入一個初始的引用變量initialRef外,還有一個initialStamp變量,initialStamp其實就是版本號(或者說時間戳),用來唯一標識引用變量。
源碼如下:
/**
* An {@code AtomicStampedReference} maintains an object reference
* along with an integer "stamp", that can be updated atomically.
*
* <p>Implementation note: This implementation maintains stamped
* references by creating internal objects representing "boxed"
* [reference, integer] pairs.
*
* @since 1.5
* @author Doug Lea
* @param <V> The type of object referred to by this reference
*/
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;
/**
* 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);
}
/**
* Returns the current value of the reference.
*
* @return the current value of the reference
*/
public V getReference() {
return pair.reference;
}
/**
* Returns the current value of the stamp.
*
* @return the current value of the stamp
*/
public int getStamp() {
return pair.stamp;
}
...
/**
* 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)));
}
...
// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe();
private static final long pairOffset =
objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class);
private boolean casPair(Pair<V> cmp, Pair<V> val) {
return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}
static long objectFieldOffset(sun.misc.Unsafe UNSAFE,
String field, Class<?> klazz) {
try {
return UNSAFE.objectFieldOffset(klazz.getDeclaredField(field));
} catch (NoSuchFieldException e) {
// Convert Exception to corresponding Error
NoSuchFieldError error = new NoSuchFieldError(field);
error.initCause(e);
throw error;
}
}
}
- 實例:
AtomicStampedReference atomicStampedReference = new AtomicStampedReference(new Integer(100), 1);
List<Thread> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
Thread thread = new Thread(() -> {
boolean flag = false;
do {
int stamp = atomicStampedReference.getStamp();
flag = atomicStampedReference.compareAndSet((Integer) atomicStampedReference.getReference(), Integer.valueOf((Integer) ((Integer) atomicStampedReference.getReference()).intValue() + 1), stamp, stamp + 1);
} while (!flag);
});
list.add(thread);
thread.start();
}
list.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println((Integer) atomicStampedReference.getReference()); // 1100
循環時間長開銷大
上面我們說過如果CAS不成功,則會原地自旋,如果長時間自旋會給CPU帶來非常大的執行開銷。
只能對單個共享變量保證原子性操作
這個時候就可以用鎖,或者有一個取巧的辦法,就是把多個共享變量合併成一個共享變量來操作。比如有兩個共享變量i=2,j=a,合併一下ij=2a,然後用CAS來操作ij。從Java1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性,你可以把多個變量放在一個對象裏來進行CAS操作。
- AtomicReference原理分析
AtomicReference就是以原子方式更新對象引用。
源碼如下
/**
* An object reference that may be updated atomically. See the {@link
* java.util.concurrent.atomic} package specification for description
* of the properties of atomic variables.
* @since 1.5
* @author Doug Lea
* @param <V> The type of object referred to by this reference
*/
public class AtomicReference<V> implements java.io.Serializable {
private static final long serialVersionUID = -1848883965231344442L;
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicReference.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile V value;
/**
* Creates a new AtomicReference with the given initial value.
*
* @param initialValue the initial value
*/
public AtomicReference(V initialValue) {
value = initialValue;
}
/**
* Creates a new AtomicReference with null initial value.
*/
public AtomicReference() {
}
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(V expect, V update) {
return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}
/**
* Atomically updates the current value with the results of
* applying the given function, returning the updated value. The
* function should be side-effect-free, since it may be re-applied
* when attempted updates fail due to contention among threads.
*
* @param updateFunction a side-effect-free function
* @return the updated value
* @since 1.8
*/
public final V updateAndGet(UnaryOperator<V> updateFunction) {
V prev, next;
do {
prev = get();
next = updateFunction.apply(prev);
} while (!compareAndSet(prev, next));
return next;
}
...
}
通過上述源碼可以看到,AtomicReference使用泛型定義。操作的是對象引用,而不是具體某個變量(AtomicInteger等),從而可以操作共享資源對象,而不單單是共享變量。(具體原理可以參考上面AtomicInteger的原理解析,大同小異,只是把int類型的value換成了對象引用V)
- 實例
AtomicReference<Integer> ref = new AtomicReference<>(new Integer(1000));
List<Thread> list = new ArrayList<>();
UnaryOperator<Integer> integerUnaryOperator = x -> x + 1;
for(int i=0;i<1000;i++){
Thread thread = new Thread( ()->{ref.updateAndGet(integerUnaryOperator);});
list.add(thread);
thread.start();
}
list.forEach( t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(ref.get()); // 2000