CAS/volatile 原理

1. 指令的 lock 前缀

intel手册对 lock 前缀的说明如下:

  1. 确保被修饰指令执行的原子性。
  2. 禁止该指令与前面和后面的读写指令重排序。
  3. 指令执行完后把写缓冲区的所有数据刷新到内存中。(这样这个指令之前的其他修改对所有处理器可见。)

在 Pentium 及之前的处理器中,带有 lock 前缀的指令在执行期间会声言 LOCK# 信号以锁住总线,使得其它处理器暂时无法通过总线访问内存,很显然,这个开销很大。在新的处理器中,Intel 使用缓存锁定来保证指令执行的原子性,缓存锁定将大大降低 lock 前缀指令的执行开销。如果访问的内存区域已经缓存在处理器内部,则不会声言 LOCK# 信号。相反地,它会锁定这块内存区域的缓存并回写到内存,并使用缓存一致性机制来确保修改的原子性,缓存一致性机制会阻止同时修改被两个以上处理器缓存的内存区域数据

2. CAS

Intel 处理器提供了 cmpxchg 指令来支持 Compare-And-Swap 操作。

JVM 根据当前系统是否为多核处理器决定是否为 cmpxchg 指令添加 lock 前缀。
1. 如果是多处理器,为 cmpxchg 指令添加 lock 前缀。
2. 反之,就省略 lock 前缀。(单处理器会不需要 lock 前缀提供的内存屏障效果)

public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2);

Unsafe.compareAndSwapInt 方法在 X86 处理器下的源代码:
c++
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
int mp = os::isMP(); //判断是否是多处理器
_asm {
mov edx, dest
mov ecx, exchange_value
mov eax, compare_value
LOCK_IF_MP(mp)
cmpxchg dword ptr [edx], ecx
}
}

LOCK_IF_MP(mp) 决定是否输出 lock 前缀。

3. volatile

如果一个字段被声明成 volatile,Java 内存模型保证所有线程看到这个变量的值是一致的。

volatile 不会引起线程上下文的切换和调度。

Java 代码: instance = new Singleton();//instance 是 volatile 变量
汇编代码: 0x01a3de1d: movb \$0x0,0x1104800(%esi);
0x01a3de24: lock addl \$0x0,(%esp);

对有 volatile 修饰的变量进行写操作时,会追加一条 lock 前缀的汇编指令。

4. 参考资料


欢迎关注我的微信公众号: coderbee笔记

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据