在Java中,当我们有两个线程共享以下变量时:
int a;
volatile int b;
如果线程1执行:
a = 5;
b = 6;
然后在这两条指令之间插入StoreStore屏障,并将'a'刷回主存。
现在,如果线程2执行:
if(b == 6)
a++;
在它之间插入了一个LoadLoad屏障,我们保证如果'b'的新值可见,那么'a'的新值也是可见的。但实际上这是如何实现的呢? LoadLoad是否使CPU高速缓存/寄存器无效?或者只是指示CPU从CPU再次获取从volatile中读取的变量的值?
我找到了有关LoadLoad barrier(http://gee.cs.oswego.edu/dl/jmm/cookbook.html)的信息:
LoadLoad Barriers序列:Load1; LoadLoad; Load2确保了这一点 Load1的数据在Load2和所有数据访问之前加载 后续加载指令已加载。一般来说,显式的LoadLoad 执行投机负载的处理器需要障碍 和/或等待加载指令可以进行的无序处理 绕过等候商店。在保证始终保留的处理器上 负载排序,障碍相当于无操作。
但它并没有真正解释如何实现这一目标。
答案 0 :(得分:4)
我将举例说明如何实现这一目标。您可以阅读详细信息here。对于x86处理器,如您所示,LoadLoad最终成为无操作。在文章中,我将Mark指出了
Doug列出了StoreStore,LoadLoad和LoadStore
因此,实质上唯一需要的障碍是用于x86架构的StoreLoad。那么这是如何在低水平上实现的呢?
这是博客的摘录:
以下是为易失性和非易失性读取生成的代码:
nop ;*synchronization entry
mov 0x10(%rsi),%rax ;*getfield x
对于易失性写入:
xchg %ax,%ax
movq $0xab,0x10(%rbx)
lock addl $0x0,(%rsp) ;*putfield x
lock
指令是Doug's cookbook列出的StoreLoad。但是锁定指令还将所有读取与其他进程同步为listed
锁定指令可用于同步一个写入的数据 处理器并由另一个处理器读取。
这减少了必须为易失性负载发出LoadLoad LoadStore障碍的开销。
所有这一切,我将重申assylias所说的话。它的发生方式对于开发人员来说应该不重要(如果您对处理器/编译器实现者感兴趣则是另一个故事)。 volatile
关键字是一种说明
答案 1 :(得分:0)
如果LoadLoad的计算结果为no-op,则线程2可以继续使用缓存值。
这由食谱中的“可以订购”表格涵盖。
编程顺序是
read b
read a
write a
通过“缓存”,表示代码已重新排序
read a
...
read b
禁止重新排序。