Java CAS原理分析

最近無意接觸了AtomicInteger類compareAndSet(從JDK5開始),搜了搜相關資料,整理了一下

首先要說一下,AtomicInteger類compareAndSet通過原子操作實現了CAS操作,最底層基於彙編語言實現。

簡單說一下原子操作的概念,“原子”代表最小的單位,所以原子操作可以看做最小的執行單位,該操作在執行完畢前不會被任何其他任務或事件打斷。

CAS是Compare And Set的一個簡稱,如下理解:

1,已知當前內存裏面的值current和預期要修改成的值new傳入

2,內存中AtomicInteger對象地址對應的真實值(因爲有可能別修改)real與current對比,

      相等表示real未被修改過,是“安全”的,將new賦給real結束然後返回;不相等說明real已經被修改,結束並重新執行1直到修改成功


CAS相比Synchronized,避免了鎖的使用,總體性能比Synchronized高很多.

compareAndSet典型使用爲計數,如i++,++i,這裏以i++爲例:

[java] view plain copy
  1. /** 
  2.  * Atomically increments by one the current value. 
  3.  * 
  4.  * @return the updated value 
  5.  */  
  6. public final int incrementAndGet() {  
  7.     for (;;) {  
  8.         //獲取當前值  
  9.         int current = get();  
  10.         //設置期望值  
  11.         int next = current + 1;  
  12.         //調用Native方法compareAndSet,執行CAS操作  
  13.         if (compareAndSet(current, next))  
  14.             //成功後纔會返回期望值,否則無線循環  
  15.             return next;  
  16.     }  
  17. }  

compareAndSet方法實現:

JDK文檔對該方法的說明如下:如果當前狀態值等於預期值,則以原子方式將同步狀態設置爲給定的更新值。

[java] view plain copy
  1. public final boolean compareAndSet(int expect, int update) {  
  2.     return unsafe.compareAndSwapInt(this, valueOffset, expect, update);  
  3. }  

這裏解釋一下valueOffset變量,首先valueOffset的初始化在static靜態代碼塊裏面,代表相對起始內存地址的字節相對偏移量:

[java] view plain copy
  1. private static final long valueOffset;  
  2.     static {  
  3.         try {  
  4.             valueOffset = unsafe.objectFieldOffset  
  5.                 (AtomicInteger.class.getDeclaredField("value"));  
  6.         } catch (Exception ex) { throw new Error(ex); }  
  7.     }  

在生成一個AtomicInteger對象後,可以看做生成了一段內存,對象中各個字段按一定順序放在這段內存中,字段可能不是連續放置的,

unsafe.objectFieldOffset(Field f)這個方法準確地告訴我"value"字段相對於AtomicInteger對象的起始內存地址的字節相對偏移量。

[java] view plain copy
  1. private volatile int value;  
  2.   
  3. /** 
  4.  * Creates a new AtomicInteger with the given initial value. 
  5.  * 
  6.  * @param initialValue the initial value 
  7.  */  
  8. public AtomicInteger(int initialValue) {  
  9.     value = initialValue;  
  10. }  
  11.   
  12. /** 
  13.  * Creates a new AtomicInteger with initial value {@code 0}. 
  14.  */  
  15. public AtomicInteger() {  
  16. }  

value是一個volatile變量,不同線程對這個變量進行操作時具有可見性,修改與寫入操作都會存入主存中,並通知其他cpu中該變量緩存行無效,保證了每次讀取都是最新的值


找到sun.misc.Unsafe.java:

[java] view plain copy
  1. /** 
  2.  * Atomically update Java variable to <tt>x</tt> if it is currently 
  3.  * holding <tt>expected</tt>. 
  4.  * @return <tt>true</tt> if successful 
  5.  */  
  6. public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);  

繼續查找unsafe.cpp,(http://hg.openjdk.java.net/jdk7/jdk7/hotspot/file/9b0ca45cd756/src/share/vm/prims/unsafe.cpp):

[cpp] view plain copy
  1. UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))  
  2.   UnsafeWrapper("Unsafe_CompareAndSwapInt");  
  3.   oop p = JNIHandles::resolve(obj);  
  4.   jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);  
  5.   return (jint)(Atomic::cmpxchg(x, addr, e)) == e;  
  6. UNSAFE_END  

實現主要方法爲Atomic::cmpxchg , 這個本地方法的最終實現在openjdk的如下位置:openjdk-7-fcs-src-b147-27jun2011\openjdk\hotspot\src\oscpu\windowsx86\vm\ atomicwindowsx86.inline.hpp(對應於windows操作系統,X86處理器) 

[cpp] view plain copy
  1. // Adding a lock prefix to an instruction on MP machine  
  2. // VC++ doesn't like the lock prefix to be on a single line  
  3. // so we can't insert a label after the lock prefix.  
  4. // By emitting a lock prefix, we can define a label after it.  
  5. #define LOCK_IF_MP(mp) __asm cmp mp, 0  \  
  6.                        __asm je L0      \  
  7.                        __asm _emit 0xF0 \  
  8.                        __asm L0:  
  9.   
  10. inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {  
  11.   // alternative for InterlockedCompareExchange  
  12.   int mp = os::is_MP();  
  13.   __asm {  
  14.     mov edx, dest  
  15.     mov ecx, exchange_value  
  16.     mov eax, compare_value  
  17.     LOCK_IF_MP(mp)  
  18.     cmpxchg dword ptr [edx], ecx  
  19.   }  
  20. }  
如上面源代碼所示,用嵌入的彙編實現的, CPU指令是 cmpxchg,程序會根據當前處理器的類型來決定是否爲cmpxchg指令添加lock前綴。如果程序是在多處理器上運行,就爲cmpxchg指令加上lock前綴(lock cmpxchg).反之,如果程序是在單處理器上運行,就省略lock前綴(單處理器自身會維護單處理器內的順序一致性,不需要lock前綴提供的內存屏障效果).lock前綴的作用說明:1禁止該指令與之前和之後的讀和寫指令重排序,2把寫緩衝區中的所有數據刷新到內存中。

總的來說,Atomic實現了高效無鎖(底層還是用到排它鎖,不過底層處理比java層處理要快很多)與線程安全(volatile變量特性),CAS一般適用於計數;多線程編程也適用,多個線程執行AtomicXXX類下面的方法,當某個線程執行的時候具有排他性,在執行方法中不會被打斷,直至當前線程完成纔會執行其他的線程。


參考文章:http://www.infoq.com/cn/articles/java-memory-model-5

                    http://hllvm.group.iteye.com/group/topic/37940

                    http://www.cnblogs.com/dolphin0520/p/3920373.html


轉自:http://blog.csdn.net/u013404471/article/details/47297123

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章