您的位置:首页 > 编程语言 > Java开发

Java原子类

2016-07-01 16:07 218 查看
概述
原子类由CAS操作保证原子性,由volatile关键字保证可见性。
原子类自jdk 1.5开始出现,位于j.u.c.atomic包下面,包含12个类,jdk 1.8又新增了4个性能更好的原子类。
可以粗略分成五类:
1.整型、长整型、布尔型、引用类型的原子类
AtomicInteger、AtomicLong、AtomicBoolean、AtomicReference
2.整型数组、长整型数组、引用数组的原子类
AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
3.整型字段、长整型字段、引用字段更新的原子类
AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
4.解决ABA问题的原子类
AtomicMarkableReference、AtomicStampedReference
5.jdk 1.8新增的更高性能的原子累加器
LongAdder、DoubleAdder、LongAccumulator、DoubleAccumulator

实现分析1
原子类的底层操作都是通过sun.misc.Unsafe类完成,每个原子类内部都有一个Unsafe类的静态引用。Unsafe类中大部分都是native方法。
private static final Unsafe unsafe = Unsafe.getUnsafe();


AtomicInteger内部由一个int域来保存值,其由volatile关键字修饰,用于保证可见性。类似的,AtomicLong内部是一个long型的value,AtomicBoolean内部也是一个int,但其只会取值0或1。
private volatile int value;


下面以AtomicInteger为例说明Atomic原子类的实现。
AtomicInteger中有一个compareAndSet方法,通过CAS对变量进行原子更新。它通过调用Unsafe的native函数实现:unsafe.compareAndSwapInt(this, valueOffset, expect, update)。compareAndSwapInt这个native方法的代码在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);
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END


其中调用了Atomic类的cmpxchg静态方法,并将返回值与e进行比较,e是期待值,即原先的旧值。cmpxchg其定义在atomic.hpp中,实现在atomic.cpp中,但最终具体的实现和目标操作系统有关,在atomic.cpp中包含了很多条件编译语句,如下。
#ifdef TARGET_OS_FAMILY_windows
# include "os_windows.inline.hpp"
#endif
....
#ifdef TARGET_OS_ARCH_linux_x86
# include "atomic_linux_x86.inline.hpp"
#endif
....


对于linux x86而言,具体实现代码在atomic_linux_x86.inline.hpp中,可以看到其中内嵌了AT&T格式的汇编。
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;
}


其中is_MP是os的静态内联方法,实现在os.cpp中,判断当前系统是否是多处理器的。
static inline bool is_MP() {
assert(_processor_count > 0, "invalid processor count");
return _processor_count > 1;
}


__asm__表示后面的代码是内嵌汇编,volatile表示编译器不要优化代码,后面的指令保持原样。内嵌汇编语的表达格式是:
__asm__(
"汇编语句模板"
:输出
:输入
:破坏描述
)


代码中的”=a”中=表示输出,而“a”表示eax寄存器,变量前的“r”表示任意寄存器,“cc”表示告诉编译器该指令的执行将影响到标志寄存器,要每次重新读取,而“memory”则是告诉编译器该指令需要重新从内存中读取变量的最新值,而不要从缓存了的寄存器中读取。

LOCK_IF_MP是个宏定义,包含了多条汇编指令(汇编中指令用分号或换行来分隔)。该宏的注释写得很清楚,如果是在多处理器机器上则添加lock前缀。
// Adding a lock prefix to an instruction on MP machine
#define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "

LOCK_IF_MP和后面的cmpxchgl指令合起来就类似如下汇编语句:
cmp $0, #mp               //$0表示立即数0,cmp是比较指令,若mp等于0则会置标志位ZF=1,ZF即Zero Flag
je 1f                              //je为跳转指令,当ZF=1时,je des表示跳转到des处,此处表示跳转到标签1
lock                              //lock前缀
1: cmpxchgl %1,(%3) //1表示一个标签,类似goto语句的标签


从上面的代码中可以看到,如果是CPU是多处理器(multi processors)的话,会添加一个lock前缀,即变成lock cmpxchgl,如果是单处理器则只需要执行cmpxchgl指令。lock前缀属于内存屏障,它的作用是在执行后面指令的过程中锁总线(或者是锁cacheline),保证一致性。后面的cmpxchgl(即Compare and Exchange,末尾的l表示操作数长度为4)就是Intel
x86的比较并交换汇编指令。Intel手册中对cmpxchg的描述(accumulator表示eax寄存器)如下:
– If (accumulator == destination)
{ ZF <- 1; destination <- source; }
– If (accumulator != destination)
{ ZF <- 0; accumulator <- destination; }

cmpxchgl的详细执行过程:
首先,输入是"r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp),表示compare_value存入eax寄存器,而exchange_value、dest、mp的值存入任意的通用寄存器。嵌入式汇编规定把输出和输入寄存器按统一顺序编号,顺序是从输出寄存器序列从左到右从上到下以“%0”开始,分别记为%0、%1···%9。也就是说,输出的eax是%0,输入的exchange_value、compare_value、dest、mp分别是%1、%2、%3、%4。
因此,cmpxchgl %1,(%3)实际上表示cmpxchgl exchange_value,(dest),此处(dest)表示dest地址所存的值。需要注意的是cmpxchgl有个隐含操作数eax,其实际过程是先比较eax的值(也就是compare_value)和dest地址所存的值是否相等,如果相等则把exchange_value的值写入dest指向的地址。如果不相等则把dest地址所存的值存入eax中。
输出是"=a" (exchange_value),表示把eax中存的值写入exchange_value变量中。
Atomic::cmpxchg这个函数最终返回值是exchange_value,也就是说,如果cmpxchgl执行时compare_value和dest指针指向内存值相等则会使得dest指针指向内存值变成exchange_value,最终eax存的compare_value赋值给了exchange_value变量,即函数最终返回的值是原先的compare_value。此时Unsafe_CompareAndSwapInt的返回值(jint)(Atomic::cmpxchg(x, addr, e)) ==
e就是true,表明CAS成功。如果cmpxchgl执行时compare_value和(dest)不等则会把当前dest指针指向内存的值写入eax,最终输出时赋值给exchange_value变量作为返回值,导致(jint)(Atomic::cmpxchg(x, addr, e)) == e得到false,表明CAS失败。

假设原值为old,存在ptr所执行的位置,想写入新值new,那么cmpxchg实现的功能就是比较old和ptr指向的内容,如果相等则ptr所指地址写入new,然后返回old,如果不相等则把ptr当前所指向地址存的值返回。

AtomicInteger还包含一系列原子的类似i++、++i、i--、--i的操作函数,即getAndIncrement、incrementAndGet、getAndDecrement、decrementAndGet。
在jdk1.6和jdk1.7中,这些函数都通过调用AtomicInteger类的compareAndSet来实现(CAS Loop,不断重试直到成功),比如getAndIncrement。
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}


在jdk1.8中,这些函数全都通过调用Unsafe类的getAndAddInt函数来实现,而getAndAddInt实际上仍然是调用了Unsafe的compareAndSwapInt函数。不过,在jdk1.8中新增的getAndUpdate、updateAndGet、getAndAccumulate、accumulateAndGet等函数都是通过AtomicInteger类的compareAndSet来实现。

AtomicInteger还有一个方法lazySet比较特别,其注释是Eventually sets to the given value,它通过unsafe的putOrderedInt函数实现,也是一个native方法。使用lazySet设置值时,不保证能立即被其他线程读取到新设置的值。它和set方法的在实现上的区别在于插入的内存屏障不同,set后面会插入store-load屏障,确保能被其他线上立即读取到,而lazySet则只插入store-store屏障,是一种底层的优化手段。Doug
Lea原话为:For people who like to think of these operations in terms of machine-level barriers on common multiprocessors, lazySet provides a preceeding store-store barrier (which is either a no-op or very cheap on current platforms), but no store-load barrier
(which is usually the expensive part of a volatile-write)."
在x86系统上,不会对store-store操作进行重排序,实际上也无需插入store-store屏障,因此lazySet最终会被编译成mov指令,而set则是mov指令后面还跟着一句lock addl $0x0,(%rsp),即插入了store-load屏障。

AtomicLong内部结构和AtomicInteger类似,其调用的是unsafe.compareAndSwapLong和unsafe.getAndAddLong,另外它比AtomicInteger多了一个静态boolean变量VM_SUPPORTS_LONG_CAS,表示底层JVM是否支持long类型(8字节)的无锁CAS,其值通过调用native方法VMSupportsCS8得到。

unsafe的compareAndSwapLong的实现也在unsafe.cpp中。如果平台支持8字节的CAS操作,则会调用Atomic::cmpxchg方法,除了返回值类型是long外,和AtomicInteger唯一的不同之处是,使用的内嵌汇编指令是cmpxchgq而不再是cmpxchgl。如果平台不支持8字节的CAS操作,就会加锁然后进行设置操作。

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapLong(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jlong e, jlong x))
UnsafeWrapper("Unsafe_CompareAndSwapLong");
Handle p (THREAD, JNIHandles::resolve(obj));
jlong* addr = (jlong*)(index_oop_from_field_offset_long(p(), offset));
if (VM_Version::supports_cx8())
return (jlong)(Atomic::cmpxchg(x, addr, e)) == e;
else {
jboolean success = false;
ObjectLocker ol(p, THREAD);
if (*addr == e) { *addr = x; success = true; }
return success;
}
UNSAFE_END


AtomicReference类内部并不是直接包含一个Object类型,它实际上是个泛型类。
public class AtomicReference<V> implements java.io.Serializable {
private volatile V value;
....
}


参考: http://brokendreams.iteye.com/blog/2250109
unsafe.cpp:http://hg.openjdk.java.net/jdk7u/jdk7u/hotspot/file/b9b4bc1e05e2/src/share/vm/prims/unsafe.cpp
atomic.hpp:http://hg.openjdk.java.net/jdk7u/jdk7u/hotspot/file/b9b4bc1e05e2/src/share/vm/runtime/atomic.hpp
atomic.cpp:http://hg.openjdk.java.net/jdk7u/jdk7u/hotspot/file/b9b4bc1e05e2/src/share/vm/runtime/atomic.cpp
atomic_linux_x86.inline.hpp:http://hg.openjdk.java.net/jdk7u/jdk7u/hotspot/file/b9b4bc1e05e2/src/os_cpu/linux_x86/vm/atomic_linux_x86.inline.hpp
os.hpp:http://hg.openjdk.java.net/jdk7u/jdk7u/hotspot/file/b9b4bc1e05e2/src/share/vm/runtime/os.hpp
Intel cmpxchg指令说明:http://heather.cs.ucdavis.edu/~matloff/50/PLN/lock.pdf
lazySet vs set:http://stackoverflow.com/questions/1468007/atomicinteger-lazyset-vs-set

实现分析2
AtomicIntegerArray内部包含一个final数组,共有2个构造函数。如果直接将普通数组传给AtomicIntegerArray,其会先复制一份数据。
private final int[] array;
public AtomicIntegerArray(int length) {
array = new int[length];
}
public AtomicIntegerArray(int[] array) {
// Visibility guaranteed by final field guarantees
this.array = array.clone();
}


AtomicIntegerArray类的方法都会比AtomicInteger对一个参数,用于指明数组下标。
AtomicIntegerArray类内部也通过Unsafe类来实现,比如get和set分别调用了Unsafe的getIntVolatile以及putIntVolatile(get、set前会先做下标是否越界的检查),即分别以volatile读和volatile写的方式实现。AtomicInteger中由于value只有一个值,直接声明为volatile,那么get和set就和普通变量类似的直接返回值或直接赋值就行,因为编译器对声明为volatile的变量自动会进行处理(插入适当的内存屏障)。而AtomicIntegerArray则不一样,由于内部是个整型数组int[],不支持直接对数组声明为volatile,因此需要在代码层显式处理。

AtomicLongArray也是类似,内部是一个final long[] array,get和set时调用的是long的版本,unsafe.getLongVolatile和unsafe.putLongVolatile。由于long是8字节,在底层实现时也会判断下平台是否支持8字节的原子操作,不支持则需要加锁。

AtomicReferenceArray和AtomicReference类似,也是一个泛型类,内部是final Object[] array。get和set时调用的unsafe.getObjectVolatile和unsafe.putObjectVolatile。

实现分析3
待续
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: