1. 程式人生 > >CASJAVA一些理解

CASJAVA一些理解

final 讀取 set als cpu 環境 情況 ola 管線

如果不用鎖機制如何實現共享數據訪問。(不要用鎖,不要 用sychronized 塊或者方法,也不要直接使用 jdk 提供的線程安全
的數據結構,需要自己實現一個類來保證多個線程同時讀寫這個類中的共享數據是線程安全的,怎麽 辦 ?)

無鎖化編程的常用方法 :件 硬件 CPU 同步原語 CAS(Compare a
nd Swap),如無鎖棧,無鎖隊列(ConcurrentLinkedQueue)等等。現在
幾乎所有的 CPU 指令都支持 CAS 的原子操作,X86 下對應的是 CMPXCHG 匯
編指令,處理器執行 CMPXCHG 指令是一個原子性操作。有了這個原子操作,
我們就可以用其來實現各種無鎖(lock free)的數據結構。

CAS 實現了區別於 sychronized 同步鎖的一種樂觀鎖,當多個線程嘗試使
用 CAS 同時更新同一個變量時,只有其中一個線程能更新變量的值,而其它線
程都失敗,失敗的線程並不會被掛起,而是被告知這次競爭中失敗,並可以再
次嘗試。CAS 有 3 個操作數,內存值 V,舊的預期值 A,要修改後的新值 B。
當且僅當預期值 A 和內存值 V 相同時,將內存值 V 修改為 B,否則什麽都不做。
其實 CAS 也算是有鎖操作,只不過是由 CPU 來觸發,比 synchronized 性能
好的多。CAS 的關鍵點在於,系統 在硬件層面保證了比較並交換操作的原子性,
處理器使用基於對緩存加鎖或總線加鎖的方式來實現多處理器之間的原子操
作。CAS 是非阻塞算法的一種常見實現。

CAS 是非阻塞算法的一種常見實現。
一個線程間共享的變量,首先在主存中會保留一份,然後每個線程的工作
內存也會保留一份副本。這裏說的預期值,就是線程保留的副本。當該線程從
主存中獲取該變量的值後,主存中該變量可能已經被其他線程刷新了,但是該
線程工作內存中該變量卻還是原來的值,這就是所謂的預期值了。當你要用 CAS
刷新該值的時候,如果發現線程工作內存和主存中不一致了,就會失敗,如果
一致,就可以更新成功。
Atomic 包提供了一系列原子類。這些類可以保證多線程環境下,當某個
線程在執行 atomic 的方法時,不會被其他線程打斷,而別的線程就像自旋鎖一

樣,一直等到該方法執行完成,才由 JVM 從等待隊列中選擇一個線程執行。
Atomic 類在軟件層面上是非阻塞的,它的原子性其實是在硬件層面上借助相關
的指令來保證的。
AtomicInteger 是一個支持原子操作的 Integer 類,就是保證對
AtomicInteger 類型變量的增加和減少操作是原子性的,不會出現多個線程下
的數據不一致問題。如果不使用 AtomicInteger,要實現一個按順序獲取的

ID,就必須在每次獲取時進行加鎖操作,以避免出現並發時獲取到同樣的 ID
的現象。Java 並發庫中的 AtomicXXX 類均是基於這個原語的實現,拿出
AtomicInteger 來研究在沒有鎖的情況下是如何做到數據正確性的:
來看看++i 是怎麽做到的。
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
在這裏采用了 CAS 操作,每次從內存中讀取數據然後將此數據和+1 後的
結果進行 CAS 操作,如果成功就返回結果,否則重試直到成功為止。
而 compareAndSet 利用 JNI 來完成 CPU 指令的操作,非阻塞算法。
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect,
update);
}
其中,unsafe.compareAndSwapInt()是一個 native 方法,正是調用
CAS 原語完成該操作。
首先假設有一個變量 i,i 的初始值為 0。每個線程都對 i 進行+1 操作。CAS
是這樣保證同步的:
假設有兩個線程,線程 1 讀取內存中的值為 0,current = 0,next = 1,然
後掛起,然後線程 2 對 i 進行操作,將 i 的值變成了 1。線程 2 執行完,回到線
程 1,進入 if 裏的 compareAndSet 方法,該方法進行的操作的邏輯是,(1)
如果操作數的值在內存中沒有被修改,返回 true,然後 compareAndSet 方法
返回 next 的值(2)如果操作數的值在內存中被修改了,則返回 false,重新
進入下一次循環,重新得到 current 的值為 1,next 的值為 2,然後再比較,
由於這次沒有被修改,所以直接返回 2。
那麽,為什麽自增操作要通過 CAS 來完成呢?仔細觀察
incrementAndGet()方法,發現自增操作其實拆成了兩步完成的:
int current = get();
int next = current + 1;
由於 volatile 只能保證讀取或寫入的是最新值,那麽可能出現以下情況:
1.A 線程執行 get()操作,獲取 current 值(假設為 1)

2.B 線程執行 get()操作,獲取 current 值(為 1)
3.B 線程執行 next = current + 1 操作,next = 2
4.A 線程執行 next = current + 1 操作,next = 2
這樣的結果明顯不是我們想要的,所以,自增操作必須采用 CAS 來完成。
CAS 的優缺點
CAS 由於是在硬件層面保證的原子性,不會鎖住當前線程,它的效
率是很高的。
CAS 雖然很高效的實現了原子操作,但是它依然存在三個問題。
1、ABA 問題。CAS 在操作值的時候檢查值是否已經變化,沒有變化的情況下
才會進行更新。但是如果一個值原來是 A,變成 B,又變成 A,那麽 CAS 進行
檢查時會認為這個值沒有變化,操作成功。ABA 問題的解決方法是使用版本號。
在變量前面追加上版本號,每次變量更新的時候把版本號加一,那麽 A-B-A
就變成 1A-2B-3A。從 Java1.5 開始 JDK 的 atomic 包裏提供了一個類
AtomicStampedReference 來解決 ABA 問題。從 Java1.5 開始 JDK 的
atomic 包裏提供了一個類 AtomicStampedReference 來解決 ABA 問題。這
個類的 compareAndSet 方法作用是首先檢查當前引用是否等於預期引用,並
且當前標誌是否等於預期標誌,如果全部相等,則以原子方式將該引用和該標
誌的值設置為給定的更新值。
CAS 算法實現一個重要前提是需要取出內存中某時刻的數據,而在下一時
刻把取出後的數據和內存中原始數據比較並替換,那麽在這個時間差內會導致
數據的變化。
比如說一個線程 one 從內存位置 V 中取出 A,這時候另一個線程 two 也從
內存中取出 A,並且 two 進行了一些操作變成了 B,然後 two 又將 V 位置的數
據變成 A,這時候線程 one 進行 CAS 操作發現內存中仍然是 A,然後 one 操
作成功。盡管線程 one 的 CAS 操作成功,但是不代表這個過程就是沒有問題
的。如果鏈表的頭在變化了兩次後恢復了原值,但是不代表鏈表就沒有變化。
因此前面提到的原子操作
AtomicStampedReference/AtomicMarkableReference 就很有用了。這允
許一對變化的元素進行原子操作。

ABA 問題帶來的隱患,各種樂觀鎖的實現中通常都會用版本
號 version 來對記錄或對象標記,避免並發操作帶來的問題。在 Java 中,
AtomicStampedReference<E>也實現了這個作用,它通過包裝[E,Integer]
的元組來對對象標記版本戳 stamp,從而避免 ABA 問題。
2、循環時間長開銷大。自旋 CAS 如果長時間不成功,會給 CPU 帶來非常大的
執行開銷。因此 CAS 不適合競爭十分頻繁的場景。
3. 只能保證一個共享變量的原子操作。當對一個共享變量執行操作時,我們可
以使用循環 CAS 的方式來保證原子操作,但是對多個共享變量操作時,循環
CAS 就無法保證操作的原子性,這個時候就可以用鎖。
這裏粘貼一個,模擬 CAS 實現的計數器:
public class CASCount implements Runnable {
private SimilatedCAS counter = new SimilatedCAS();
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println(this.increment());
}
}

public int increment() {
int oldValue = counter.getValue();
int newValue = oldValue + 1;
while (!counter.compareAndSwap(oldValue, newValue)) { //
如果 CAS 失敗,就去拿新值繼續執行 CAS
oldValue = counter.getValue();
newValue = oldValue + 1;
}
return newValue;
}
public static void main(String[] args) {
Runnable run = new CASCount();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
}
class SimilatedCAS {
private int value;
public int getValue() {
return value;
}
// 這裏只能用 synchronized 了,畢竟無法調用操作系統的 CAS
public synchronized boolean compareAndSwap(int expectedValue,
int newValue) {
if (value == expectedValue) {
value = newValue;
return true;
}
return false;
}
}

CASJAVA一些理解