珠海建设网站的公司简介/百度排名点击软件
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 = JNIHandles::resolve(obj);if (p == NULL) {volatile jint* addr = (volatile jint*)index_oop_from_field_offset_long(p, offset);return RawAccess<>::atomic_cmpxchg(x, addr, e) == e;} else {assert_field_offset_sane(p, offset);return HeapAccess<>::atomic_cmpxchg_at(x, p, (ptrdiff_t)offset, e) == e;}
} UNSAFE_END
5、atomic_cmpxchg()方法
转到/openjdk/src/hotspot/os_cpu/linux_x86/atomic_linux_x86.hpp文件中,查看atomic_cmpxchg具体的系统平台的实现,源码如下:
template<>
template<typename T>
inline T Atomic::PlatformCmpxchg<8>::operator()(T exchange_value,T volatile* dest,T compare_value,atomic_memory_order /* order */) const {STATIC_ASSERT(8 == sizeof(T));__asm__ __volatile__ ("lock cmpxchgq %1,(%3)": "=a" (exchange_value): "r" (exchange_value), "a" (compare_value), "r" (dest): "cc", "memory");return exchange_value;
}
到这里已经是汇编指令的级别代码了。
“r” (exchange_value), “a” (compare_value), “r” (dest):表示将compare_value的值存入eax寄存器,exchange_value、dest存入任意的通用寄存器。
“=a” (exchange_value):表示将rax寄存器的值赋值给exchange_value变量。
lock:lock指令就是锁总线,来保证这块内存的互斥性。
cmpxchgq %1,(%3):实际上是cmpxchgq exchange_value, (dest)。首先把比较eax寄存器的值和dest的值是否相等,如果相等,则把exchange_value的值写入到dest指向的地址中,如果不相等,则把dest中的值写入到eax寄存器中。最中返回exchange_value,而exchange_value存储的是eax寄存器中的值,
总结
本文主要是对JAVA多线程中CAS问题进行了一系列的探讨。内容设计面较广,深入JAVA源码以及C++源码,深入了解了CAS工作的原理。