在java中,如果要進行原子操作,我們可以通過加鎖或CAS的方式來實現。其中,CAS雖然高效的解決了原子操作,但需要注意其存在的三個問題:ABA問題、自旋時間長開銷大、只能保證一個共享變量的原子操作。
一、ABA問題
CAS 在修改變量值時,會先檢查該變量的值是否和預期值一致,若一致則修改,引發的ABA問題的情況是:如一個變量初始值爲A,被另外一個線程修改成B,再由B修改爲A,此時使用CAS進行操作就檢查不出變量的變化軌跡,並對該變量修改,這就是ABA問題,其前提條件是“節點可以被循環使用”。
ABA問題的解決辦法是在變量前加上版本號,每次變量的修改都將版本號加1,此時 A → B → A 則變成 1A → 2B → 3A。從而避免ABA問題。
JDK 1.5 之後再併發包下也提供了對應的帶郵戳的原子類 AtomicStampedReference以解決ABA問題。
/**
* 原子設置 reference 和 stamp 爲給定的更新值,
* 當且當前的引用== 期望的引用,stamp和期望的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,// 期望的stamp
int newStamp // 新的stamp
) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
二、自旋時間長開銷大
如果CAS自旋長時間仍不成功,對CPU的開銷是非常大的。
三、只能保證一個共享變量的原子操作
CAS可以保證單個共享變量的原子操作,對於多個共享變量,CAS無法保證原子性,當然此時可以使用鎖來解決。另外一個方法是:把多個共享變量封裝進一個對象,然後通過AtomicReference類保證引用對象之間的原子性。
參考:
1.《Java併發編程的藝術》-Java併發機制的底層實現原理(第2章)
2.《JAVA併發編程實戰》,Doug lea