synchronized
关键字了,可是
synchronized
属于重量级锁,不少时候会引发性能问题,
volatile
也是个不错的选择,可是
volatile
不能保证原子性,只能在某些场合下使用。
像synchronized
这种独占锁属于悲观锁,它是在假设必定会发生冲突的,那么加锁刚好有用,除此以外,还有乐观锁,乐观锁的含义就是假设没有发生冲突,那么我正好能够进行某项操做,若是要是发生冲突呢,那我就重试直到成功,乐观锁最多见的就是CAS
。java
咱们在读Concurrent包下的类的源码时,发现不管是ReenterLock内部的AQS,仍是各类Atomic开头的原子类,内部都应用到了CAS
,最多见的就是咱们在并发编程时遇到的i++
这种状况。传统的方法确定是在方法上加上synchronized
关键字:编程
public class Test {
public volatile int i;
public synchronized void add() {
i++;
}
}
复制代码
可是这种方法在性能上可能会差一点,咱们还能够使用AtomicInteger
,就能够保证i
原子的++
了。bash
public class Test {
public AtomicInteger i;
public void add() {
i.getAndIncrement();
}
}
复制代码
咱们来看getAndIncrement
的内部:并发
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
复制代码
再深刻到getAndAddInt
():app
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
复制代码
这里咱们见到compareAndSwapInt
这个函数,它也是CAS
缩写的由来。那么仔细分析下这个函数作了什么呢?函数
首先咱们发现compareAndSwapInt
前面的this
,那么它属于哪一个类呢,咱们看上一步getAndAddInt
,前面是unsafe
。这里咱们进入的Unsafe
类。这里要对Unsafe
类作个说明。结合AtomicInteger
的定义来讲:oop
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
...
复制代码
在AtomicInteger
数据定义的部分,咱们能够看到,其实实际存储的值是放在value
中的,除此以外咱们还获取了unsafe
实例,而且定义了valueOffset
。再看到static
块,懂类加载过程的都知道,static
块的加载发生于类加载的时候,是最早初始化的,这时候咱们调用unsafe
的objectFieldOffset
从Atomic
类文件中获取value
的偏移量,那么valueOffset
其实就是记录value
的偏移量的。源码分析
再回到上面一个函数getAndAddInt
,咱们看var5
获取的是什么,经过调用unsafe
的getIntVolatile(var1, var2)
,这是个native方法,具体实现到JDK源码里去看了,其实就是获取var1
中,var2
偏移量处的值。var1
就是AtomicInteger
,var2
就是咱们前面提到的valueOffset
,这样咱们就从内存里获取到如今valueOffset
处的值了。性能
如今重点来了,compareAndSwapInt(var1, var2, var5, var5 + var4)
其实换成compareAndSwapInt(obj, offset, expect, update)
比较清楚,意思就是若是obj
内的value
和expect
相等,就证实没有其余线程改变过这个变量,那么就更新它为update
,若是这一步的CAS
没有成功,那就采用自旋的方式继续进行CAS
操做,取出乍一看这也是两个步骤了啊,其实在JNI
里是借助于一个CPU
指令完成的。因此仍是原子操做。优化
CAS底层使用JNI
调用C代码实现的,若是你有Hotspot
源码,那么在Unsafe.cpp
里能够找到它的实现:
static JNINativeMethod methods_15[] = {
//省略一堆代码...
{CC"compareAndSwapInt", CC"("OBJ"J""I""I"")Z", FN_PTR(Unsafe_CompareAndSwapInt)},
{CC"compareAndSwapLong", CC"("OBJ"J""J""J"")Z", FN_PTR(Unsafe_CompareAndSwapLong)},
//省略一堆代码...
};
复制代码
咱们能够看到compareAndSwapInt实现是在Unsafe_CompareAndSwapInt
里面,再深刻到Unsafe_CompareAndSwapInt
:
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
复制代码
p是取出的对象,addr是p中offset处的地址,最后调用了Atomic::cmpxchg(x, addr, e)
, 其中参数x是即将更新的值,参数e是原内存的值。代码中能看到cmpxchg有基于各个平台的实现,这里我选择Linux X86平台下的源码分析:
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;
}
复制代码
这是一段小汇编,__asm__
说明是ASM汇编,__volatile__
禁止编译器优化
// Adding a lock prefix to an instruction on MP machine
#define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "
复制代码
os::is_MP
判断当前系统是否为多核系统,若是是就给总线加锁,因此同一芯片上的其余处理器就暂时不能经过总线访问内存,保证了该指令在多处理器环境下的原子性。
在正式解读这段汇编前,咱们来了解下嵌入汇编的基本格式:
asm ( assembler template
: output operands /* optional */
: input operands /* optional */
: list of clobbered registers /* optional */
);
复制代码
template就是cmpxchgl %1,(%3)
表示汇编模板
output operands表示输出操做数,=a
对应eax寄存器
input operand 表示输入参数,%1
就是exchange_value
, %3
是dest
, %4
就是mp
, r
表示任意寄存器,a
仍是eax
寄存器
list of clobbered registers就是些额外参数,cc
表示编译器cmpxchgl
的执行将影响到标志寄存器, memory
告诉编译器要从新从内存中读取变量的最新值,这点实现了volatile
的感受。
那么表达式其实就是cmpxchgl exchange_value ,dest
,咱们会发现%2
也就是compare_value
没有用上,这里就要分析cmpxchgl
的语义了。cmpxchgl
末尾l
表示操做数长度为4
,上面已经知道了。cmpxchgl
会默认比较eax
寄存器的值即compare_value
和exchange_value
的值,若是相等,就把dest
的值赋值给exchange_value
,不然,将exchange_value
赋值给eax
。具体汇编指令能够查看Intel手册CMPXCHG
最终,JDK经过CPU的cmpxchgl
指令的支持,实现AtomicInteger
的CAS
操做的原子性。
CAS须要在操做值的时候检查下值有没有发生变化,若是没有发生变化则更新,可是若是一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,可是实际上却变化了。这就是CAS的ABA问题。 常见的解决思路是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A
就会变成1A-2B-3A
。 目前在JDK的atomic包里提供了一个类AtomicStampedReference
来解决ABA问题。这个类的compareAndSet方法做用是首先检查当前引用是否等于预期引用,而且当前标志是否等于预期标志,若是所有相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
上面咱们说过若是CAS不成功,则会原地自旋,若是长时间自旋会给CPU带来很是大的执行开销。