若是一個線程失敗或掛起不會致使其餘線程也失敗或掛起,那麼這種算法就被稱爲非阻塞算法。而CAS就是一種非阻塞算法實現,也是一種樂觀鎖技術。它能在不使用鎖的狀況下實現多線程之間的變量同步,因此CAS也是一種無鎖算法。html
CAS包含了3個操做數——須要讀寫的內存位置V、進行比較的值A和擬寫入的新值B。當且僅當V的值等於A時,CAS纔會經過原子方式用新值B來更新V的值,不然不會寫入新值,下面來看模擬CAS的實現。java
//CAS模擬實現
public class SimulatedCAS {
private int value;
public synchronized int get() {
return value;
}
public synchronized int compareAndSwap(int expectedValue, int newValue) {
int oldValue = value;
if (oldValue == expectedValue) {
value = newValue;
}
return oldValue;
}
public synchronized boolean compareAndSet(int expectedValue, int newValue) {
return (expectedValue == compareAndSwap(expectedValue, newValue));
}
}
複製代碼
SimulatedCAS
就是模擬CAS的實現,由於這裏僅僅是模擬CAS,明白其語義,因此代碼仍是很簡單的。算法
//基於CAS實現的一個線程安全計數器
public class CasCounter {
private SimulatedCAS value;
public int getValue() {
return value.get();
}
public int increment() {
int v;
do {
v = value.get();
} while (v != value.compareAndSwap(v, v + 1));
return v + 1;
}
}
複製代碼
CasCounter
就是基於SimulatedCAS
實現的一個線程安全的計數器,CasCounter
不會阻塞,但若是其餘線程同時更新更新計數器,那麼將會屢次執行重試操做。理論上,若是其餘線程在每次競爭CAS時老是獲勝,那麼這個線程每次都會重試,但實際上不多發生這種類型的飢餓問題。編程
Java5.0引入了底層的支持,在int、long和對象引用等類型上也都公開了CAS操做,而且JVM把它們編譯爲底層硬件提供的最有效方法。在支持CAS的平臺,運行時把它們編譯爲相應的(多條)機器指令。最壞的狀況下,若是不支持CAS指令,那麼JVM將使用自旋鎖。所以Java提供的CAS解決方案就與平臺/編譯器緊密相關,其性能也受平臺/編譯器影響。數組
原子變量類(例如java.util.concurrent.atomic
中的AtomicXxx
)就使用了這種高效的CAS操做。安全
類型 | 具體實現類 | 描述 |
---|---|---|
基本數據類型 | AtomicInteger | 原子更新Integer類型 |
AtomicBoolean | 原子更新boolean類型 | |
AtomicLong | 原子更新long類型 | |
數組類型 | AtomicIntegerArray | 原子更新Integer數組類型 |
AtomicLongArray | 原子更新long數組類型 | |
AtomicReferenceArray | 原子更新對象數組類型 | |
引用類型 | AtomicReference | 原子更新對象 |
AtomicStampedReference | 原子更新對象,解決ABA問題 | |
AtomicMarkableReference | 原子更新對象,解決ABA問題 | |
更新字段類型 | AtomicIntegerFieldUpdater | 原子更新已存在的volatile修飾的Integer類型,使用反射實現 |
AtomicLongFieldUpdater | 原子更新已存在的volatile修飾的long類型,使用反射實現 | |
AtomicReferenceFieldUpdater | 原子更新已存在的volatile修飾的對象,使用反射實現 |
上面就是Java中的全部原子變量類,基本上都比較好理解,可是個別的仍是須要說明一下。多線程
AtomicStampedReference
與AtomicMarkableReference
都是爲了解決ABA問題而存在的。但它們是有區別的,AtomicStampedReference
是給對象引用上加一個版本號來避免ABA問題,而AtomicMarkableReference
是給對象引用加上一個標記(boolean類型)來避免ABA問題的。併發
更新字段類型又叫原子的域更新器,表示現有volatile
的一種基於反射的「視圖」,從而可以在已有的volatile
域上使用CAS。它提供的原子性保證比普通原子類更弱一些,由於沒法直接保證底層的域不被直接修改——compareAndSet
以及其餘算術方法只能確保其餘使用更新器方法的線程的原子性。app
下面來看AtomicInteger
的源碼。工具
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能夠說是value在JVM中的虛擬內存地址
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 get() {
return value;
}
//設置新值
public final void set(int newValue) {
value = newValue;
}
//1.6新增方法,設置新值,但不會當即刷新,性能比set方法好
public final void lazySet(int newValue) {
unsafe.putOrderedInt(this, valueOffset, newValue);
}
//設定新值並返回舊值
public final int getAndSet(int newValue) {
return unsafe.getAndSetInt(this, valueOffset, newValue);
}
//比較並替換,Java CAS實現方法
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
//比較並替換,Java CAS實現方案,與compareAndSet實現同樣名單不排除將來會有不一樣實現
public final boolean weakCompareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
//以原子方式將當前值增長1。返回舊值
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
//以原子方式將當前值減1。返回舊值
public final int getAndDecrement() {
return unsafe.getAndAddInt(this, valueOffset, -1);
}
//以原子方式將給定值添加到當前值。返回舊值
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
//以原子方式將當前值增長1。返回新值
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
//以原子方式將當前值減1。返回新值
public final int decrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
}
//以原子方式將給定值添加到當前值。返回新值
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
//1.8新增方法,更新並返回舊值
public final int getAndUpdate(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return prev;
}
//1.8新增方法,更新並返回新值
public final int updateAndGet(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return next;
}
//1.8新增方法,更新並返回舊值
public final int getAndAccumulate(int x, IntBinaryOperator accumulatorFunction) {
int prev, next;
do {
prev = get();
next = accumulatorFunction.applyAsInt(prev, x);
} while (!compareAndSet(prev, next));
return prev;
}
//1.8新增方法,更新並返回新值
public final int accumulateAndGet(int x, IntBinaryOperator accumulatorFunction) {
int prev, next;
do {
prev = get();
next = accumulatorFunction.applyAsInt(prev, x);
} while (!compareAndSet(prev, next));
return next;
}
//都是些類型轉換的實現
...
}
複製代碼
valueOffset
是變量在JVM中的內存地址,也就是CAS中所說的內存位置V。set
與lazySet
、compareAndSet
與weakCompareAndSet
這兩組方法須要注意一下。
因爲value
是volatile
修飾的,因此當調用set
方法時,全部線程的value
值會當即更新,而lazySet
則不會,只有在從新寫入新值的時候纔會更新,而讀取的仍是舊值。關於這兩個方法的區別能夠參考J.U.C原子工具類AtomicXXX中,set和lazySet的區別、AtomicInteger lazySet vs. set這兩篇文章。
compareAndSet
與weakCompareAndSet
這兩個方法就比較有意思,由於實現如出一轍的,那是爲何尼?在Stack Overflow上說,Java在將來可能會有不一樣的實現,關於這兩個方法的區別能夠參考java – 若是weakCompareAndSet若是實現徹底像compareAndSet,會如何失敗?這篇文章。
在AtomicInteger
及其餘原子變量類中,都是使用Unsafe
來實現CAS的,該類大部分都是native方法且不對外公開,由於是不安全的。該類與硬件相關,都是CPU指令級的操做,只有一步原子操做,並且CAS避免了請求操做系統來裁定鎖的問題,不用麻煩操做系統,直接在CPU內部就搞定了,因此效率很是高且線程安全。
CAS主要有如下問題。
AtomicReference
來解決。ABA問題是CAS中的一個異常現象,來回顧一下CAS的語義,當且僅當V的值等於A時,CAS纔會經過原子方式用新值B來更新V的值,不然不會執行任何操做。那麼若是先將V的值改成C而後再改成A,那麼其餘線程的CAS操做仍然可以成功,但這樣對嗎?很明顯是不對的,由於V的值變化了。那麼如何解決尼?其實能夠在變量前面添加版本號,每次變量更新的時候都把版本號加一,這樣變化過程就從「A-B-A」變成了「1A-2B-3A」。
在Java中,CAS的運用仍是比較普遍的,如對象建立的原子性、Java 1.8中ConcurrentHashMap
及RxJava的多線程變量同步都是經過CAS來實現的。
【參考資料】
非阻塞同步算法與CAS(Compare and Swap)無鎖算法