對於併發控制而言,咱們平時用的鎖(synchronized,Lock)是一種悲觀的策略。它老是假設每一次臨界區操做會產生衝突,所以,必須對每次操做都當心翼翼。若是多個線程同時訪問臨界區資源,就寧肯犧牲性能讓線程進行等待,因此鎖會阻塞線程執行。java
與之相對的有一種樂觀的策略,它會假設對資源的訪問是沒有衝突的。既然沒有衝突也就無需等待了,全部的線程都在不停頓的狀態下持續執行。那若是遇到問題了無鎖的策略使用一種叫作比較交換(CAS Compare And Swap)來鑑別線程衝突,一旦檢測到衝突產生,就重試當前操做直到沒有衝突。CAS算法是非阻塞的,它對死鎖問題天生免疫,並且它比基於鎖的方式擁有更優越的性能。算法
CAS算法的過程是這樣:它包含三個參數 CAS(V,E,N)。V表示要更新的變量,E表示預期的值,N表示新值。僅當V值等於E值時,纔會將V的值設置成N,不然什麼都不作。最後CAS返回當前V的值。CAS算法須要你額外給出一個指望值,也就是你認爲如今變量應該是什麼樣子,若是變量不是你想象的那樣,那說明已經被別人修改過。你就從新讀取,再次嘗試修改便可。安全
JDK併發包有一個atomic包,裏面實現了一些直接使用CAS操做的線程安全的類型。其中最經常使用的一個類應該就是AtomicInteger。咱們以此爲例來研究一下沒有鎖的狀況下如何作到線程安全。併發
private volatile int value;
性能
這是AtomicInteger類的核心字段,表明當前實際取值,藉助volatile保證線程間數據的可見性。this
獲取內部數據的方法:atom
public final int get() { return value; }
咱們從源碼的實現看看incrementAndGet()
的內部實現 線程
public final int incrementAndGet() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return next; } }
代碼第二行使用了一個死循環,緣由是:
CAS的操做未必都是成功的,所以對於不成功的狀況,咱們就須要進行不斷的嘗試。
第三行取得當前值,接着+1獲得新值next。
這裏咱們使用CAS必需的兩個參數:指望值以及新值。
使用compareAndSet()將新值next寫入。成功的條件是在寫入的時刻當前的值應該要等於剛剛取到的current。若是不是這樣則說明AtomicInteger的值在第3行到第5行之間被其餘線程修改過了。當前看到的狀態是一個過時的狀態,所以返回失敗,須要進行下一次重試,直到成功爲止。code
public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
總體的過程就是這樣子,利用CPU的CAS指令,同時藉助JNI來完成Java的非阻塞算法。其它原子操做都是利用相似的特性完成的。大概的邏輯應該是這樣:對象
if (this == expect) { this = update return true; } else { return false; }
CAS雖然能高效的解決原子問題,可是CAS也會帶來1個經典問題即ABA問題:
由於CAS須要在操做值的時候檢查下值有沒有發生變化,若是沒有發生變化則更新,可是若是一個值原來是A,變成了B,又變成了A,那麼使用CAS進行檢查時會發現它的值沒有發生變化,可是實際上卻變化了。
ABA問題的解決思路就是使用版本號。在變量前面追加上版本號,每次變量更新的時候把版本號加一,那麼A-B-A 就會變成1A-2B-3A。
從Java1.5開始JDK的atomic包裏提供了一個類AtomicStampedReference來解決ABA問題。這個類在內部不只維護了對象值,還維護了一個時間戳(能夠是任意的一個整數來表示狀態值)。當設置對象值時,對象值和狀態值都必須知足指望值纔會寫入成功。所以即便對象被反覆讀寫,寫會原值,只要狀態值發生變化,就能防止不恰當的寫入。
/** * @param expectedReference 指望值 * @param newReference 寫入新值 * @param expectedStamp 指望狀態值 * @param newStamp 新狀態值 * @return true if successful */ public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair<V> current = pair; return expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp))); }
我的公衆號:JAVA日知錄 , javadaily.cn