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, long var2, int var4, int var5); public final native boolean compareandswaplong(object var1, long var2, long var4, long var6); public native object getobjectvolatile(object var1, long var2); public native void putobjectvolatile(object var1, long var2, 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, volatile jint* dest, 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较高
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。