CAS(Compare And Swap)
獨佔鎖是一種悲觀鎖,而 synchronized 就是一種獨佔鎖,synchronized 會導致其它所有未持有鎖的線程阻塞,而等待持有鎖的線程釋放鎖。所謂樂觀鎖就是,每次不加鎖而是假設沒有衝突而去完成某項操作,如果因爲衝突失敗就重試,直到成功爲止。而樂觀鎖用到的機制就是CAS。在java中可以通過鎖和循環CAS的方式來實現原子操作。java.util.concurrent.atomic包相關類就是CAS的實現。
CAS機制當中使用了3個基本操作數:內存地址V(value,volatile修飾的變量),舊的預期值A(expected),要修改的新值B(updated)。
更新一個變量的時候,只有當前變量的預期值cur和內存地址V中的實際值(value)相同時,纔會將內存地址V對應的值(value)修改爲B(updated)。
- CAS機制實現自增(保證原子性)
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);//獲取當前值,相當與上面提到的預期值A
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); //比較當前值與實際值,var5+var4相當於updated
return var5; //返回swap後的值
}
getAndAddInt中的內容是:
1. 獲取當前值var5通過getIntVolatile()。
2. while循環,如果當前值var5 等於 實際值value,則compareAndSwapInt返回true,將實際值更新後,並跳出循環。若不等於,則進入循環,嘗試重新獲取當前值,將當前值重新與實際值compare,這個重新嘗試的過程被稱爲自旋。
3. 返回交換後的值。
從思想上來說,Synchronized屬於悲觀鎖,悲觀地認爲程序中的併發情況嚴重,所以嚴防死守。CAS屬於樂觀鎖,樂觀地認爲程序中的併發情況不那麼嚴重,所以讓線程不斷去嘗試更新。
package cn.itcast.thread;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class Counter {
private AtomicInteger ai = new AtomicInteger();
private int i = 0;
/** 使用CAS實現線程安全計數器 */
private void safeCount() {
for (;;) {
int i = ai.get();
// 如果實際值(執行到這一步時,從內存V中獲取的value) == 預期值(上一步獲取的當前值),則以原子方式將該值設置爲給定的更新值
boolean suc = ai.compareAndSet(i, ++i); //第一個參數:expected value;第二個參數:updated value
if (suc) {
break;
}
}
}
/** 非線程安全計數器 */
private void count() {
i++;
}
public static void main(String[] args) {
final Counter cas = new Counter();
List<Thread> ts = new ArrayList<Thread>();
// 添加1000個線程
for (int j = 0; j < 1000; j++) {
ts.add(new Thread(new Runnable() {
public void run() {
// 執行1000次計算,預期結果應該是1000000
for (int i = 0; i < 1000; i++) {
cas.count();
cas.safeCount();
}
}
}));
}
//開始執行
for (Thread t : ts) {
t.start();
}
// 等待所有線程執行完成
for (Thread t : ts) {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 非線程安全計數結果:999689
* 線程安全計數結果:1000000
*/
System.out.println("非線程安全計數結果:"+ cas.i);
System.out.println("線程安全計數結果:"+ cas.ai.get());
}
}
正是unsafe(提供硬件級別的原子操作)的compareAndSwapInt方法保證了Compare和Swap操作之間的原子性操作。(比較返回爲true才進行替換,否則不執行任何操作(自旋))
CAS的缺點
- 循環時間開銷大:自旋CAS如果長時間不成功,會給CPU帶來非常大的執行開銷。
- 只能保證一個共享變量的原子操作:當對一個共享變量執行操作時,可以使用循環CAS的方式來保證原子操作,但對於多個共享變量操作時,循環CAS就無法保證操作的原子性。這時候就可以用鎖;或者一個取巧的方式,就是把多個共享變量合併成一個對象來操作。從java1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性,可以把多個變量放在一個對象裏來進行CAS操作。
- ABA問題:因爲CAS需要在操作值的時候檢查下值有沒有發生變化,如果沒有發生變化則更新,但是如果一個值原來是A,變成了B,又變成了A,那麼使用CAS進行檢查時會發現它的值沒有發生變化,但是實際上卻變化了。ABA問題的解決思路就是使用版本號。在變量前面追加上版本號,每次變量更新的時候把版本號加一,那麼A-B-A 就會變成1A-2B-3A。 從Java1.5開始JDK的 atomic包裏提供了一個類AtomicStampedReference 來解決ABA問題。這個類的 compareAndSet方法作用是首先檢查當前引用是否等於預期引用,並且當前標誌是否等於預期標誌,如果全部相等,則以原子方式將該引用和該標誌的值設置爲給定的更新值。(改用傳統的互斥同步可能會比原子類更高效)