JMM:本身并不真实存在, 只是一种抽象概念。关于同步的规定:
- 线程加锁前,必须读取主内存最新值到自己的工作内存;
- 线程解锁前,必须把共享变量刷回主内存
- 加锁解锁,同一把锁。
JMM要求三种性质: 可见性,原子性,有序性。
volatile
保证了可见性和有序性, 但是不能保证原子性: e.g. i++
;
AtomicInteger
类创建出来的对象atomicInteger
的方法GetAndIncrement()
可以满足原子性地执行i++
; 因为底层用了CAS
的原理,实际上是通过Unsafe
类,和自旋
操作完成的。
为什么AtomicInteger
底层用的CAS
而不是synchronized
? 因为自旋
操作,在保证一致性的同时,提高了并发性。
AtomicInteger.java
:
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
点进getAndAddInt
:
/*
var1: addr <- new AtomicInteger() var2: valueOffset
var5: value; var4: value to add (1 here)
*/
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
可以看到是先得到var5
, 再compareAndSwapInt
; compareAndSwapInt
会一直比较工作内存和主内存中那个需要修改的值,只有expected==value
的时候才会修改成功,否则会一直自旋重新拿主物理内存中的值,直到expected==value
的时候才会修改成功.
能保证原子性,是因为1. Unsafe.class
类;2. 自旋
的写法;这一类修改值方法底层原理就是CAS
思想.
更细致: 具体为什么getIntVolatile(var1, var2)
和compareAndSwapInt(var1, var2, var5, var5 + var4)
就可以? 这两个方法本身是怎么写的?
public native int getIntVolatile(Object var1, long var2);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
都是native
方法。
CAS
全称是Compare-And-Swap
;是一条CPU并发原语
, 体现在Java中sun.mics.Unsafe
类中的各个native
方法,属于操作系统用语,由若干指令组成, 并且原语的执行必须是连续的,不被中断, i.e. CAS是一条CPU原子指令,不会造成数据不一致问题。 调用Unsafe
类中的CAS方法,JVM帮我们实现CAS汇编指令: e.g. 优于加锁,并保证原子性
Unsafe API
: 大多数API都是通过native
函数实现的,具体可以查看源码中的文件path/to/openjdk/source/hotspot/src/share/vm/prims/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
i.e. 拿到value
在内存中的地址,通过Atomic::cmpxchg
实现比较替换,其中参数x
是即将更新的值,参数e
是原内存的值。
就是靠的Atomic::cmpxchg
底层汇编保证原子性
简单来说,CAS就是比较当前线程工作内存中的值和主内存中值,相同就执行操作,否则继续比较直到主内存和工作内存中的值一致。
synchronized
: 保证一致性,并发性下降;
CAS
: 不加锁,保证一致性和并发性(do...while
自旋代码实现的一致性), 但是可能多次比较
CAS
缺点:
- 可能长时间在循环♻️中自旋, CPU开销大;
- 只能保证一个共享变量的原子性;
- ABA问题