AtomicBoolean是一个原子boolen类,可以用于高并发场景下的标记处理,例如某个只需初始化一次的服务:
public class SomeService {
private String name;
private AtomicBoolean initialized = new AtomicBoolean(false);
public String getName() {
return name;
}
public void init(String name){
if(initialized.compareAndSet(false,true)){
this.name = name; // 初始化工作
}
}
}
这样的效果是普通boolean类型无法达到的,除非使用重量级的synchronized关键字进行同步。
AtomicBoolean内部有很多这样组合的原子操作:
public final boolean compareAndSet(boolean expect, boolean update);
public final boolean getAndSet(boolean newValue)
以compareAndSet为例,它包含了两个操作,先比较,如果等于期望值则进行更新。在AtomicBoolean内部是通过Unsafe这个类实现这一系列原子操作的。
public final boolean compareAndSet(boolean expect, boolean update) {
int e = expect ? 1 : 0;
int u = update ? 1 : 0;
return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}
Unsafe类是一个非常强大的类,不仅提供了一些原子操作,还提供了内存操作,类似于C++的指针功能。Unsafe类正如其名,是一个不安全的类,使用Unsafe类会使得程序更容易出问题,因此Java官方并不建议使用的,源码也没有任何注释。
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
在Unsafe中,compareAndSet这个方法参数由2个变成了4个,AtomicBoolean对象自己,值在内存中的偏移量,期待值和更新值。compareAndSet(CAS)被标记为native,这个方法是通过JNI技术,由jvm里的C++/C语言实现的。我去翻了下HotSpot-1.6的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
在实现里,参数由4个变成了6六个,多了JNI环境,unsafe对象两个参数。里面调用了Atomic的cmpxchg方法,这个方法在不同平台下的实现不一样,在Linux x86系统中,在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;
}
os::is_MP()表示是否是多处理器系统,比较简单。单处理器=0,多处理器=1。从asm开始,就涉及到C++内联汇编代码,比较复杂了。我们先做一下解释。
linux x86中的基本的内联汇编写法是:
__asm__ [__volatile__] ("instruction list");
asm :它是GCC定义的关键字asm的宏定义(#define asm asm),它用来声明一个内联汇编表达式,所以任何一个内联汇编表达式都以它开头。
volatile:它是GCC关键字volatile的宏定义,这个选项是可选的,它向GCC声明"不要动我所写的instruction list,我需要原封不动地保留每一条指令",如果不使用__volatile__,则当你使用了优化选项-O进行优化编译时,GCC将会根据自己的判断来决定是否将这个内联汇编表达式中的指令优化掉。
instruction list:它是汇编指令列表,可以包括多条指令。如果是将所有指令写在一对双引号中,则相邻两条指令之间必须用分号";"或换行符(\n)隔开。
而我们这里的汇编是带有C++表达式的内联汇编,它稍微有一点不同,它包含了指定输出输入寄存器等功能。基本语法如下:
__asm__ [__volatile__]("instruction list":Output:Input:Clobber/Modify);
: “=a” (exchange_value) 表示将结果写入%eax、%ax或者%al寄存器中(由GCC决定)。
: “r” (exchange_value), “a” (compare_value), “r” (dest), “r” (mp) 表示将exchange_value、dest、mp读入任意的通用寄存器中,compare_value读入%eax寄存器中。
: “cc”, “memory” 中"cc"表示向GCC声明,标志寄存器eflags可能做了修改;“memory"表示向GCC声明"我对内存做了改动”,这样,GCC在编译的时候就会将此因素考虑进去。
关于内联汇编更多的知识和解释可以参考这篇文章:linux x86的内联汇编。
LOCK_IF_MP表示如果是多处理器,那么会为cmpxchgl指令添加LOCK前缀。
#define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "
LOCK_IF_MP宏使用cmp指令和je指令一起控制是否添加LOCK前缀。cmp指令比较0和mp的值(0减去mp,如果结果为0则标志寄存器的零标志位ZF=1,否则ZF=0)。je是跳转指令,根据ZF进行跳转。如果是单处理器,标志位ZF=1,je指令就会向后跳转到1:标签的位置。如果是多处理器,标志位ZF=0,je指令就不会跳转,执行后面的lock指令。
使用了LOCK前缀后,CPU会锁住总线(CPU上有一根pin #HLOCK连到北桥,lock前缀会在执行这条指令前先去拉这根pin,持续到这个指令结束时放开#HLOCK pin),保证该内存区域的独占时访问,确保了一系列指令操作的原子性。
会什么会在C++代码里面加入内联汇编呢?因为这样的原子操作,C++也搞不定,只能通过汇编指令来借助硬件的力量。所以,我们得出结论:AtomicBoolean借助了底层硬件的力量来实现了原子操作。
其实不仅仅是AtomicBoolean,整个java并发包都建立在CAS+volatile这两个基石之上。