作者:淇蒴_192 | 来源:互联网 | 2023-06-12 05:33
JAVA多线程中,线程安全问题是随处可见的问题,JAVA早期版本使用synchronized来保证线程同步。JDK1.5之后引入CAS。
文章目录 一、CAS的概念 二、以AtomicInteger.java为例 1、getAndIncrement()方法 2、getAndAddInt()方法 3、weakCompareAndSetInt()方法 4、compareAndSetInt()方法 5、atomic_cmpxchg()方法 总结
一、CAS的概念 CAS(全称:compare and swap,比较和及交换),CAS至少需要三个操作数,当前内存位置(VALUE)、预期的值(expectedValue)、新值(newValue),如图。当多个线程对同一个变量修改的时候,CAS的做法是:找到当前内存的存储的值,然后和预期的值进行比较,如果当前内存位置的值和expectedValue相等,则自动将该值设置为newValue,如果不可相等,将再次获取当前位置的值比较,直到当前内存的值和expectedValue相等,然后才将内存的值设置为newValue。简单的说,就是不断的进行比较,直到成功后设置新值,保证设置新值的时候不会去覆盖别的线程的设置的值。
二、以AtomicInteger.java为例 JAVA中对AtomicInteger.java的注释如下:可以原子方式更新int的值,也就是说,AtomicInteger.java这个类中,涉及到的相关方法在操作行基本上都是原子性的,基本都能保证线程安全问题。
1、getAndIncrement()方法 JAVA中对getAndIncrement()的注释如下:使用VarHandle.getAndAdd指定的内存效果,以原子方式递增当前值,相当于getAndAdd(1)。也就是说,当我们调用getAndIncrement()的时候,相当于系统帮我们以原子操作进行了i++操作。
2、getAndAddInt()方法 当我们调用getAndIncrement()时,方法会调用Unsafe.java类中的getAndAddInt()方法,源码如下:
public final int getAndIncrement ( ) { return U. getAndAddInt ( this , VALUE, 1 ) ; } public final int getAndAddInt ( Object o, long offset, int delta) { int v; do { v = getIntVolatile ( o, offset) ; } while ( ! weakCompareAndSetInt ( o, offset, v, v + delta) ) ; return v; }
v相当于是i++中的i,o是当前对象,也就是AtomicInteger.java对象,因为这个o是从上层传递下来的,offset为偏移量,在UnSafe.java中,通过获取o+offset的位置的值,然后调用weakCompareAndSetInt()方法来进行原子操作。
3、weakCompareAndSetInt()方法 到这里我们可以发现,其实,v就是我们期望的值,x就是我们想要改变的值。
public final boolean weakCompareAndSetInt ( Object o, long offset, int expected, int x) { return compareAndSetInt ( o, offset, expected, x) ; }
4、compareAndSetInt()方法 compareAndSetInt()方法是一个native方法,也就是用c++来实现的,我们需要在JDK官网上下载JDK源码,找到/openjdk/src/hotspot/share/prims/unsafe.cpp文件中的Unsafe_CompareAndSetInt()方法,实际上它调用了atomic_cmpxchg()方法。compareAndSetInt()返回一个boolean类型的值,这个boolean类型的值是atomic_cmpxchg()返回的寄存器中的值和新值比较的结果。如果相等,就说明CAS成功,新值设置成功,返回true,否则就说明CAS失败,新值设置失败,返回false。源码如下:
UNSAFE_ENTRY ( jboolean, Unsafe_CompareAndSetInt ( JNIEnv * env, jobject unsafe, jobject obj, jlong offset, jint e, jint x) ) { oop p &#61; JNIHandles:: resolve ( obj) ; if ( p &#61;&#61; NULL ) { volatile jint* addr &#61; ( volatile jint* ) index_oop_from_field_offset_long ( p, offset) ; return RawAccess< > :: atomic_cmpxchg ( x, addr, e) &#61;&#61; e; } else { assert_field_offset_sane ( p, offset) ; return HeapAccess< > :: atomic_cmpxchg_at ( x, p, ( ptrdiff_t) offset, e) &#61;&#61; e; } } UNSAFE_END
5、atomic_cmpxchg()方法 转到/openjdk/src/hotspot/os_cpu/linux_x86/atomic_linux_x86.hpp文件中&#xff0c;查看atomic_cmpxchg具体的系统平台的实现&#xff0c;源码如下&#xff1a;
template < > template < typename T> inline T Atomic:: PlatformCmpxchg< 8 > :: operator ( ) ( T exchange_value, T volatile * dest, T compare_value, atomic_memory_order ) const { STATIC_ASSERT ( 8 &#61;&#61; sizeof ( T) ) ; __asm__ __volatile__ ( "lock cmpxchgq %1,(%3)" : "&#61;a" ( exchange_value) : "r" ( exchange_value) , "a" ( compare_value) , "r" ( dest) : "cc" , "memory" ) ; return exchange_value; }
到这里已经是汇编指令的级别代码了。 “r” (exchange_value), “a” (compare_value), “r” (dest)&#xff1a;表示将compare_value的值存入eax寄存器&#xff0c;exchange_value、dest存入任意的通用寄存器。 “&#61;a” (exchange_value)&#xff1a;表示将rax寄存器的值赋值给exchange_value变量。 lock&#xff1a;lock指令就是锁总线&#xff0c;来保证这块内存的互斥性。 cmpxchgq %1,(%3)&#xff1a;实际上是cmpxchgq exchange_value, (dest)。首先把比较eax寄存器的值和dest的值是否相等&#xff0c;如果相等&#xff0c;则把exchange_value的值写入到dest指向的地址中&#xff0c;如果不相等&#xff0c;则把dest中的值写入到eax寄存器中。最中返回exchange_value&#xff0c;而exchange_value存储的是eax寄存器中的值&#xff0c;
总结 本文主要是对JAVA多线程中CAS问题进行了一系列的探讨。内容设计面较广&#xff0c;深入JAVA源码以及C&#43;&#43;源码&#xff0c;深入了解了CAS工作的原理。