無鎖演算法
CAS, CPU指令,在大多數處理器架構,包括IA32、Space中採用的都是CAS指令,CAS的語義是“我認為V的值應該為A,如果是,那麼將V的值更新為B,否則不修改並告訴V的值實際為多少”,CAS是項 樂觀鎖 技術,當多個執行緒嘗試使用CAS同時更新同一個變數時,只有其中一個執行緒能更新變數的值,而其它執行緒都失敗,失敗的執行緒並不會被掛起,而是被告知這次競爭中失敗,並可以再次嘗試。CAS有3個運算元,記憶體值V,舊的預期值A,要修改的新值B。當且僅當預期值A和記憶體值V相同時,將記憶體值V修改為B,否則什麼都不做。
無鎖程式設計,即不使用鎖的情況下實現多執行緒之間的變數同步,也就是在沒有執行緒被阻塞的情況下實現變數的同步, 所以也叫非阻塞同步(Non-blocking Synchronization)。實現非阻塞同步的方案稱為“無鎖程式設計演算法”( Non-blocking algorithm)。
相對應的,獨佔鎖是一種悲觀鎖,synchronized就是一種獨佔鎖,它假設最壞的情況,並且只有在確保其它執行緒不會造成干擾的情況下執行,會導致其它所有需要鎖的執行緒掛起, 等待持有鎖的執行緒釋放鎖。
- 使用lock實現執行緒同步有很多缺點:
- 產生競爭時,執行緒被阻塞等待,無法做到執行緒實時響應。
- dead lock,死鎖。
- live lock。
- 優先順序翻轉。
- 使用不當,造成效能下降。 當然在部分情況下,目前來看,無鎖程式設計並不能替代 lock。
實現級別
非同步阻塞的實現可以分成以下三個級別:
- wait-free
- 是最理想的模式,整個操作保證每個執行緒在有限步驟下完成。
- 保證系統級吞吐(system-wide throughput)以及無執行緒飢餓。
- 截止2011年,沒有多少具體的實現。即使實現了,也需要依賴於具體CPU。
- lock-free
- 允許個別執行緒飢餓,但保證系統級吞吐。確保至少有一個執行緒能夠繼續執行。
- wait-free的演算法必定也是lock-free的。
- obstruction-free
- 在任何時間點,一個執行緒被隔離為一個事務進行執行(其他執行緒suspended),並且在有限步驟內完成。
- 在執行過程中,一旦發現資料被修改(採用時間戳、版本號),則回滾。也叫做樂觀鎖,即樂觀併發控制(OOC)。
- 事務的過程是:
- 讀取,並寫時間戳;
- 準備寫入,版本校驗;
- 校驗通過則寫入,校驗不通過,則回滾。
- lock-free必定是obstruction-free的。
底層介紹
java.util.concurrent.atomic中的AtomicXXX,都使用了這些底層的JVM支援為數
字型別的引用型別提供一種高效的CAS操作,而在java.util.concurrent中的大多數類在實現時都直接或間接的使用了這些原子變數類,這些原子變數都呼叫了 sun.misc.Unsafe 類庫裡面的 CAS演算法,用CPU指令來實現無鎖自增.
//JDK原始碼:
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
複製程式碼
因而在大部分情況下,java中使用Atomic包中的incrementAndGet的效能比用synchronized高出幾倍。
ABA問題
-
問題描述 thread1意圖對val=1進行操作變成2,cas(val,1,2)。 thread1先讀取val=1;thread1被搶佔(preempted),讓thread2執行。 thread2 修改val=3,又修改回1。 thread1繼續執行,發現期望值與“原值”(其實被修改過了)相同,完成CAS操作。
-
解決方案
- ABA:新增額外的標記用來指示是否被修改。 從Java1.5開始JDK的atomic包裡提供了一個類AtomicStampedReference來解決ABA問題。這個類的compareAndSet方法作用是首先檢查當前引用是否等於預期引用,並且當前標誌是否等於預期標誌,如果全部相等,則以原子方式將該引用和該標誌的值設定為給定的更新值。