最近面試被問到一個問題,AtomicInteger如何保證線程安全?我查閱了資料 發現還可以引申到 樂觀鎖/悲觀鎖的概念,覺得值得一記。
眾所周知,JDK提供了AtomicInteger保證對數字的操作是線程安全的,線程安全我首先想到了synchronized和Lock,但是這種方式又有一個名字,叫做互斥鎖,一次只能有一個持有鎖的線程進入,再加上還有不同線程爭奪鎖這個機制,效率比較低,所以又稱“悲觀鎖”。
但是相應的有了樂觀鎖的概念,他的思路就是,它不加鎖去完成某項操作,如果因為沖突失敗就重試,直到成功為止。這種說的比較抽象,我們直接拿AtomicInteger源碼舉例,因為AtomicInteger保證線程安全就是因為使用了樂觀鎖。
Unsafe 是做一些Java語言不允許但是又十分有用的事情,具體的實現都是native方法,AtomicInteger里調用的 Unsafe 方法 基於的是CPU 的 CAS指令來實現的。所以基於 CAS 的操作可認為是無阻塞的,一個線程的失敗或掛起不會引起其它線程也失敗或掛起。並且由於 CAS 操作是 CPU 原語,所以性能比較好。
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
1
2
3
4
5
6
7
8
CAS就是Compare and Swap的意思,比較並操作。很多的cpu直接支持CAS指令。CAS是項樂觀鎖技術,當多個線程嘗試使用CAS同時更新同一個變量時,只有其中一個線程能更新變量的值,而其它線程都失敗,失敗的線程並不會被掛起,而是被告知這次競爭中失敗,並可以再次嘗試。CAS有3個操作數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改為B,否則什么都不做。 從代碼上我們可以看到do while語句,從而證實當更新出現沖突時,即失敗時,它還會嘗試更新。符合樂觀鎖的思想。
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;
}
1
2
3
4
5
6
7
8
最后比較一下 樂觀鎖/悲觀鎖的 區別
樂觀鎖適用於寫比較少的情況下,即沖突比較少發生,這樣可以省去了鎖的開銷,加大了系統的整個吞吐量。
但如果經常產生沖突,樂觀鎖 的重復嘗試 反倒會降低了性能,所以這種情況下用悲觀鎖就比較合適。