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

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