1. 程式人生 > 程式設計 >Java CAS底層實現原理例項詳解

Java CAS底層實現原理例項詳解

這篇文章主要介紹了Java CAS底層實現原理例項詳解,文中通過示例程式碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下

一、CAS(compareAndSwap)的概念

CAS,全稱Compare And Swap(比較與交換),解決多執行緒並行情況下使用鎖造成效能損耗的一種機制。

CAS(V,A,B),V為記憶體地址、A為預期原值,B為新值。如果記憶體地址的值與預期原值相匹配,那麼將該位置值更新為新值。否則,說明已經被其他執行緒更新,處理器不做任何操作;無論哪種情況,它都會在 CAS 指令之前返回該位置的值。而我們可以使用自旋鎖,迴圈CAS,重新讀取該變數再嘗試再次修改該變數,也可以放棄操作。

二、CAS(compareAndSwap)的產生

為什麼需要CAS機制呢?我們先從一個錯誤現象談起。我們經常使用volatile關鍵字修飾某一個變數,表明這個變數是全域性共享的一個變數,同時具有了可見性和有序性。但是卻沒有原子性。比如說一個常見的操作a++。這個操作其實可以細分成三個步驟:

(1)從記憶體中讀取a

(2)對a進行加1操作

(3)將a的值重新寫入記憶體中

在單執行緒狀態下這個操作沒有一點問題,但是在多執行緒中就會出現各種各樣的問題了。因為可能一個執行緒對a進行了加1操作,還沒來得及寫入記憶體,其他的執行緒就讀取了舊值。造成了執行緒的不安全現象。

Volatile關鍵字可以保證執行緒間對於共享變數的可見性可有序性,可以防止CPU的指令重排序(DCL單例),但是無法保證操作的原子性,所以jdk1.5之後引入CAS利用CPU原語保證執行緒操作的院子性。

CAS操作由處理器提供支援,是一種原語。原語是作業系統或計算機網路用語範疇。是由若干條指令組成的,用於完成一定功能的一個過程,具有不可分割性,即原語的執行必須是連續的,在執行過程中不允許被中斷。如 Intel 處理器,比較並交換通過指令的 cmpxchg 系列實現。

三、CAS(compareAndSwap)的原理探究

CAS的實現主要在JUC中的atomic包,我們以AtomicInteger類為例:

通過程式碼追溯,可以看出JAVA中的CAS操作都是通過sun包下Unsafe類實現,而Unsafe類中的方法都是native方法,由JVM本地實現,所以最終的實現是基於C、C++在作業系統之上操作

Unsafe類,在sun.misc包下,不屬於Java標準。Unsafe類提供一系列增加Java語言能力的操作,如記憶體管理、操作類/物件/變數、多執行緒同步等

//var1為CAS操作的物件,offset為var1某個屬性的地址偏移值,expected為期望值,var2為要設定的值,利用JNI來完成CPU指令的操作
public final native boolean compareAndSwapObject(Object var1,long var2,Object var4,Object var5);
public final native boolean compareAndSwapInt(Object var1,int var4,int var5);
public final native boolean compareAndSwapLong(Object var1,long var4,long var6);
public native Object getObjectVolatile(Object var1,long var2);
public native void putObjectVolatile(Object var1,Object var4);
Hotspot原始碼中關於unsafe的實現hotspot\src\share\vm\prims\unsafe.cpp
UNSAFE_ENTRY(jboolean,Unsafe_CompareAndSwapInt(JNIEnv *env,jobject unsafe,jobject obj,jlong offset,jint e,jint x))
 UnsafeWrapper("Unsafe_CompareAndSwapInt");
 oop p = JNIHandles::resolve(obj);根據偏移量,計算value的地址。這裡的offset就是 AtomaicInteger中的valueOffset
 jint* addr = (jint *) index_oop_from_field_offset_long(p,offset);
 return (jint)(Atomic::cmpxchg(x,addr,e)) == e;
UNSAFE_END\hotspot\src\share\vm\runtime\atomic.cppunsigned Atomic::cmpxchg(unsigned int exchange_value,volatile unsigned int* dest,unsigned int compare_value) {
 assert(sizeof(unsigned int) == sizeof(jint),"more work to do");
 return (unsigned int)Atomic::cmpxchg((jint)exchange_value,(volatile jint*)dest,(jint)compare_value);
}根據作業系統型別呼叫不同平臺下的過載函式,這個在預編譯期間編譯器會決定呼叫哪個平臺下的過載

可以看到呼叫了“Atomic::cmpxchg”方法,“Atomic::cmpxchg”方法在linux_x86和windows_x86的實現如下

linux_x86底層實現\hotspot\src\os_cpu\linux_x86\vm\atomic_linux_x86.inline.hpp
inline jint   Atomic::cmpxchg  (jint   exchange_value,volatile jint*   dest,jint   compare_value) {
 int mp = os::is_MP();
 __asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
          : "=a" (exchange_value)
          : "r" (exchange_value),"a" (compare_value),"r" (dest),"r" (mp)
          : "cc","memory");
 return exchange_value;
}
windows_x86底層實現
hotspot\src\os_cpu\windows_x86\vmatomic_linux_x86.inline.hpp
inline jint   Atomic::cmpxchg  (jint   exchange_value,jint   compare_value) {
 // alternative for InterlockedCompareExchange
 int mp = os::is_MP();
 __asm {
  mov edx,dest
  mov ecx,exchange_value
  mov eax,compare_value
  LOCK_IF_MP(mp)
  cmpxchg dword ptr [edx],ecx
 }
}

總結:根據資料查詢,其實CAS底層實現根據不同的作業系統會有不同過載,CAS的實現離不開處理器的支援。

核心程式碼就是一條帶lock 字首的 cmpxchg 指令,即lock cmpxchg dword ptr [edx],ecx

Atomic::cmpxchg方法解析:

mp是“os::is_MP()”的返回結果,“os::is_MP()”是一個行內函數,用來判斷當前系統是否為多處理器。

如果當前系統是多處理器,該函式返回1。

否則,返回0。

LOCK_IF_MP(mp)會根據mp的值來決定是否為cmpxchg指令新增lock字首。

如果通過mp判斷當前系統是多處理器(即mp值為1),則為cmpxchg指令新增lock字首。

否則,不加lock字首。

這是一種優化手段,認為單處理器的環境沒有必要新增lock字首,只有在多核情況下才會新增lock字首,因為lock會導致效能下降。cmpxchg是彙編指令,作用是比較並交換運算元。

四、CAS機制的優缺點

4.1 優點

cas是一種樂觀鎖,而且是一種非阻塞的輕量級的樂觀鎖,什麼是非阻塞式的呢?其實就是一個執行緒想要獲得鎖,對方會給一個迴應表示這個鎖能不能獲得。在資源競爭不激烈的情況下效能高,相比synchronized重量鎖,synchronized會進行比較複雜的加鎖,解鎖和喚醒操作。

4.2 缺點

1)迴圈時間長開銷大,佔用CPU資源

2)只能保證一個共享變數的原子操作

3)ABA問題

4.3 解決ABA問題

1)新增版本號

2)AtomicStampedReference

java併發包為了解決這個問題,提供了一個帶有標記的原子引用類“AtomicStampedReference”,它可以通過控制變數值的版本來保證CAS的正確性。因此,在使用CAS前要考慮清楚“ABA”問題是否會影響程式併發的正確性,如果需要解決ABA問題,改用傳統的互斥同步可能會比原子類更高效。

五、CAS使用的時機

5.1 執行緒數較少、等待時間短可以採用自旋鎖進行CAS嘗試拿鎖,較於synchronized高效

5.2 執行緒數較大、等待時間長,不建議使用自旋鎖,佔用CPU較高

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。