在併發編程中,對於共用資源的使用需要確保絕對的安全性。除了利用鎖機制之外,還有一種無鎖的概念。所謂無鎖,就是假定在併發情況下,對於共用資源的訪問沒有衝突,線程可以一直不停的運行,無需阻塞,如果產生衝突,則使用CAS演算法確保全全性。Java在很多併發代碼中都使用了這種演算法。 CAS演算法的核心參數如下: ...
併發編程時,對於共用資源的使用需要確保絕對的安全性。除了利用鎖機制之外,還有一種無鎖的概念。所謂無鎖,就是假定在併發情況下,對於共用資源的訪問沒有衝突,線程可以一直不停的運行,無需阻塞,如果產生衝突,則使用CAS演算法確保全全性。Java在很多併發代碼中都使用了這種演算法。
CAS演算法的核心參數如下:
compareAndSet(V,E,A)
V代碼需要進行更新的變數;E代表預期值;A代表所要更新的值。
CAS的核心思想就是:當要對一個變數進行更新時,先取出該變數此時在記憶體中的實際值,與預期值進行比較。如果相等,則代表沒有其他線程對其進行過修改,直接更新。如果不同,表示有其他線程修改過,此時有兩種策略。一種是讓CAS自旋,直到更新成功;另外表示當前線程更新失敗,繼續後續邏輯。大概示意圖如下:
對於CAS自旋,有可能會出現長時間更新失敗,浪費CPU性能的情況。JDK1.6以後,加入了適應性自旋的概念。即如果某個鎖自旋時很少獲得成功,那麼會減少自旋次數。自旋次數的預設值是10次,可以使用參數-XX:PreBlockSpin來更改。
再說一下為什麼CAS在更新變數值的時候不會受到其他線程的影響呢?會不會我在判斷更新值與預期值相等,進行更新的過程中,變數被其他線程更新了呢?其實不會,因為CAS具有排它性,一次CAS是一個原子操作,是CPU源語級別。確保隔離性。
Java對於CAS是使用Unsafe類進行支持的。顧名思義,不安全,可以讓程式員像C語言那樣直接操縱記憶體指針。java.util.concurrent.atomic包下的類,都是使用CAS實現的。
ABA缺陷
不知道大家通過上面對CAS的介紹說明,看到了CAS演算法的缺陷沒有!
CAS本身會有ABA問題。舉個例子,變數m = 10,我要把m更新為30。在我判斷之前,有其他線程先把m更新到50,在從50更新到10。那麼我進行 10 == 10的判斷時,這時候我怎麼確定m的值有沒有被改過呢?這就是ABA問題了。
要解決這個問題,我們就需要在進行 10 == 10判斷的時候,還需要有其他參照。Java中有一個類AtomicStampedReference,這個類除了維護變數值之外,還會維護一個時間戳。用於確定數值版本。
private static class Pair<T> { final T reference; final int stamp; private Pair(T reference, int stamp) { this.reference = reference; this.stamp = stamp; } static <T> Pair<T> of(T reference, int stamp) { return new Pair<T>(reference, stamp); } } private volatile Pair<V> pair;
用volatile關鍵字修飾的內部類。
看一下核心的compareAndSet()方法:
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))); }
可以看到除了比較數值之外,還會比較時間戳。