cas——compareAndSwap
概念
- compareAndSwap翻译过来就是 比较并交换
- cas底层 调用的是unSafe,unSafed对底层的修改调用的native方法(CPU并发原语),天然原子性
代码说话
- 创建一个AtomicInteger类,初始化值5,此时线程A去修改,把5读到工作内存,修改成2000,在写回主内存时,会比较当时拿到工作内存的5和现在主内存是否一致, 一致则修改成2000,不一致则表示被其他线程修改了。
- 底层代码
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;
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
// 这里会做自旋
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;
}
-
假设线程A和线程B同时执行getAndAddInt操作
-
1 AtomicInteger里面的value原始值为3,即主内存中Atomicinteger的value=3,根据Jmm模型,线程A和线程B各自持有一份值为3的value的副本拷贝到各自的工作内存。
-
2 线程A通过getIntVolatile(var1, var2)拿到value值3,这时线程A被挂起。
-
3 线程B通过getIntVolatile(var1,var2)拿到value值3,此时线程B得到CPU执行权,执行compareAndSwapint方法比较内存值也为3,成功修改成4 ,此时主内存值为4了
-
4 线程A恢复,执行compareSwapint方法比较,发现自己手里的值3和主内存不一致了,本次修改失败,重新读取再来一遍
-
5 线程A重新获取value值,因为变量value被volatile修饰,是可见的,compareSwapint方法成功,修改成功。
-
UnSafe类的理解
- UnSafe
-
它是CAS的核心类,由于java方法无法直接访问底层系统,需要通过本地方法来访问,Unsage相当于是一个后门,基于该类可以直接操作特定内存的数据。
-
Unsafe类存在于sun.misc包中,其 内部方法操作可以像C的指针一样直接操作内存
-
Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应的任务
-
- compareAndSwapInt
-
该方法的实现位于unsafe.cpp中
-
使用cmpxchg指令比较并更新值(保证原子性),下面是底层写法
// obj 是当前工作内存的值 // offset 是获取主内存的值 oop p = JNIHandles::resolve(obj) jint* addr = (jint *) index_oop_from_field_offset_long(p, offset) (Atomic::cmpxchg(x, addr, e))
-
AtomicInteger是如何保证线程安全
-
变量valueOffset 标识该变量值在内存中的 偏移地址,因为Unsafe就是根据内存 偏移地址 来获取数据的
public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } 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; }
-
value用volatile修饰,保证了内存之间的可见性
private volatile int value;
-
总结
- 利用CAS(底层是靠硬件层面的锁) + volatile来保证原子性,从而避免synchronized的高开销,提升执行效率
cas的缺点
- 当前线程如果每次比较并交换的时候都返回false,就会一直请求,会给CPU带来很大的开销
- ABA问题
- 当线程A去读主内存的值为1, 另一个线程B修改了这个值为2并写回主内存,然后又重新修改回1写回主内存,然后线程A进行修改写回主内存是可以成功的
- 尽管cas成功了,但是不代表这个过程是没问题的
- 解决:
- 通过添加版本号的方式
- 如:AtomicReference会有ABA问题、AtomicStampedReference没有ABA问题
底层深究
- CPU并发原语为什么就能保证原子性 (借鉴)
-
CPU 处理器速度远远大于在主内存中的,为了解决速度差异,在他们之间架设了多级缓存,如 L1、L2、L3 级别的缓存,这些缓存离CPU越近就越快,将频繁操作的数据缓存到这里,加快访问速度
-
现在都是多核 CPU 处理器,每个 CPU 处理器内维护了一块字节的内存,每个内核内部维护着一块字节的缓存,当多线程并发读写时,就会出现缓存数据不一致的情况
-
总线锁定
-
当一个处理器要操作共享变量时,在 BUS 总线上发出一个 Lock 信号,其他处理就无法操作这个共享变量了。
-
缺点很明显,总线锁定在阻塞其它处理器获取该共享变量的操作请求时,也可能会导致大量阻塞,从而增加系统的性能开销。
-
-
缓存锁定
-
后来的处理器都提供了缓存锁定机制,也就说当某个处理器对缓存中的共享变量进行了操作,其他处理器会有个嗅探机制,将其他处理器的该共享变量的缓存失效,待其他线程读取时会重新从主内存中读取最新的数据,基于 MESI 缓存一致性协议来实现的。
-
MESI是Modified、Exclusive、Shared、Invalid这四个单词的首字母。这4个字母分别代表4种状态
状态 描述 监听任务 Modified 该缓存行有效,但是该缓存数据已经被当前核心修改,此时和DRAM中数据不一致。我们将其置为M,其他的核中缓存行都会置为I。 监听总线上所有对该缓存行写回DRAM的操作(不希望别人写入),需要将该操作延迟到自己将缓存行写回到主存后变成S状态。 Excluside(互斥) 该缓存行有效,数据和RAM的数据一致,数据只存在当前内核工作内存中,只有他在使用是独占的。 监听总线上所有从DRAM读取该缓存行的操作,一旦有读的,需要将状态置为S状态。 Shared(共享) 该缓存行有效,不过当前缓存行在多个核中都有,并且大家以及DRAM中的都一样。 监听其他的缓存中将该缓存置为I或者为E的事件,将状态置为I状态。 Invalid(无效) 表明该缓存行无效,如果想要获取数据的话,就去DRAM中加载最新的。 不需要监听。
-
-
现代的处理器基本都支持和使用的缓存锁定机制
-
-