1. 程式人生 > >併發程式設計中有關CAS操作的學習記錄

併發程式設計中有關CAS操作的學習記錄

什麼是悲觀鎖、樂觀鎖?在java語言裡,總有一些名詞看語義跟本不明白是啥玩意兒,也就總有部分面試官拿著這樣的詞來忽悠面試者,以此來找優越感,其實理解清楚了,這些詞也就唬不住人了。

  • synchronized是悲觀鎖,這種執行緒一旦得到鎖,其他需要鎖的執行緒就掛起的情況就是悲觀鎖。
  • CAS操作的就是樂觀鎖,每次不加鎖而是假設沒有衝突而去完成某項操作,如果因為衝突失敗就重試,直到成功為止。

那麼問題來了,什麼是CAS操作?

CAS是Compare-and-swap(比較與替換)的簡寫,是一種有名的無鎖演算法

比較並操作,CPU指令,在大多數處理器架構,包括IA32、Space中採用的都是CAS指令,CAS的語義是“我認為V的值應該為A,如果是,那麼將V的值更新為B,否則不修改並告訴V的值實際為多少”,CAS是項樂觀鎖技術,當多個執行緒嘗試使用CAS同時更新同一個變數時,只有其中一個執行緒能更新變數的值,而其它執行緒都失敗,失敗的執行緒並不會被掛起,而是被告知這次競爭中失敗,並可以再次嘗試。CAS有3個運算元,記憶體值V,舊的預期值A,要修改的新值B。當且僅當預期值A和記憶體值V相同時,將記憶體值V修改為B,否則什麼都不做。


相關術語:

這裡寫圖片描述

這裡寫圖片描述

2.處理器是如何實現原子操作

32位IA-32處理器使用基於對快取加鎖或匯流排加鎖的方式來實現多處理器之間的原子操作。首先處理器會自動保證基本的記憶體操作的原子性。處理器保證從系統記憶體中讀取或寫入一個位元組是原子的,意思是當一個處理器讀取一個位元組時,其他處理器不能訪問這個位元組的記憶體地址。

2.1 匯流排鎖定

如果多個處理器一起對共享變數進行讀改寫操作(i++就是典型的讀改寫操作),這個讀改寫操作就不是原子的。如上圖所示,各個處理器將i的值讀入自己的處理器快取中,各自對各自的快取裡的i值進行操作,然後分別寫入系統記憶體從而導致了問題的產生。要想對共享變數的讀改寫操作也是原子性的,必須保證,CPU1讀改寫共享變數的時候,CPU2不能操作該共享變數記憶體地址的快取行。

處理器的匯流排鎖就是這麼來保證原子性的,所謂匯流排鎖就是使用處理器提供的一個LOCK #訊號,當一個處理器在總線上輸出此訊號時,其他處理器的請求將會被阻塞住,那麼該處理器可以獨佔共享記憶體。

2.2 快取鎖定

在同一時刻我們只需要保證對某一個記憶體地址的操作是原子性就可以了,但是匯流排鎖把CPU和記憶體之間的通訊鎖住了,這使得鎖定期間,其他處理器不能操作其他記憶體地址的資料,所以匯流排鎖定的開銷比較大。

頻繁使用的記憶體會快取在處理器的L1,L2,L3快取記憶體中,那麼原子操作就可以直接在處理器內部快取進行,並不需要宣告匯流排鎖。

快取鎖是:一個處理器的快取寫回到記憶體會導致其他處理器的快取無效,在多核處理器中,例如在Pentium和P6 family處理中,如果通過嗅探一個處理器來檢測到其他處理器打算寫記憶體地址,而這個地址當前處於共享狀態,那麼正在嗅探的處理器將使它的快取行無效,在下次訪問相同記憶體地址時,強制執行快取行填充。

但是有兩種情況是不能使用快取鎖:一是不能快取到處理器的資料以及跨多個快取行的資料;而是有些處理器不支援快取鎖定。
 

看一看compareAndSet方法的實現,以及方法所依賴物件的來歷:

compareAndSet方法的實現很簡單,只有一行程式碼。這裡涉及到兩個重要的物件,一個是unsafe,一個是valueOffset

什麼是unsafe呢?Java語言不像C,C++那樣可以直接訪問底層作業系統,但是JVM為我們提供了一個後門,這個後門就是unsafe。unsafe為我們提供了硬體級別的原子操作

至於valueOffset物件,是通過unsafe.objectFieldOffset方法得到,所代表的是AtomicInteger物件value成員變數在記憶體中的偏移量。我們可以簡單地把valueOffset理解為value變數的記憶體地址。

我們在上一期說過,CAS機制當中使用了3個基本運算元:記憶體地址V,舊的預期值A,要修改的新值B

而unsafe的compareAndSwapInt方法引數包括了這三個基本元素:valueOffset引數代表了V,expect引數代表了A,update引數代表了B。

正是unsafe的compareAndSwapInt方法保證了Compare和Swap操作之間的原子性操作。

unsafe:對應的是Unsafe類,Java無法直接訪問底層作業系統,而是通過本地(native)方法來訪問。JDK中有一個類Unsafe,它提供了硬體級別的原子操作。JDK API文件也沒有提供任何關於這個類的方法的解釋。從描述可以瞭解到Unsafe提供了硬體級別的操作,比如說獲取某個屬性在記憶體中的位置,比如說修改物件的欄位值,即使它是私有的。

value:volatile修飾的變數,記憶體中其他執行緒具有可見性。加或減都是對這個變數值進行修改。

valueOffset:這裡指的就是value這個屬性在記憶體中的偏移量(記憶體中的地址,而不是值),當類被載入時先按順序初始化static變數和static塊,通過unsafe中public native long objectFieldOffset(Field paramField);

/** Returns the memory address offset of the given static field.

   * The offset is merely used as a means to access a particular field
   * in the other methods of this class.  The value is unique to the given
   * field and the same value should be returned on each subsequent call.
   * 返回指定靜態field的記憶體地址偏移量,在這個類的其他方法中這個值只是被用作一個訪問
   * 特定field的一個方式。這個值對於 給定的field是唯一的,並且後續對該方法的呼叫都應該
   * 返回相同的值。
   *
   * @param field the field whose offset should be returned.
   *              需要返回偏移量的field
   * @return the offset of the given field.
   *         指定field的偏移量
   */
  public native long objectFieldOffset(Field field);

獲取AtomicInteger類屬性value在記憶體中的偏移量,並將偏移量值賦給valueOffset。需要強調valueOffset代表的不是value值在記憶體中的位置,而是這個屬性在記憶體中的地址。

參考資料: