2017-07-17 57 views
1

我书面方式这篇文章中的连接上下文的JIT产生的x86输出到Deep understanding of volatile in Java分析中的挥发性

public class Main { 
    private int x; 
    private volatile int g; 


    public void actor1(){ 
     x = 1; 
     g = 1; 
    } 


    public void actor2(){ 
     put_on_screen_without_sync(g); 
     put_on_screen_without_sync(x); 
    } 
} 

现在,我分析了上面这段代码生成了什么JIT。从我们在我以前的帖子讨论中,我们知道,输出1, 0是不可能的,因为:


写挥发性v使每一个动作a前述v导致该a将是可见的(将被刷新到内存)v之前将可见。


.................(I removed not important body of method)..... 

    0x00007f42307d9d5e: c7460c01000000  (1) mov  dword ptr [rsi+0ch],1h 
               ;*putfield x 
               ; - package.Main::[email protected] (line 14) 

    0x00007f42307d9d65: bf01000000   (2) mov  edi,1h 
    0x00007f42307d9d6a: 897e10    (3) mov  dword ptr [rsi+10h],edi 
    0x00007f42307d9d6d: f083042400   (4) lock add dword ptr [rsp],0h 
               ;*putfield g 
               ; - package.Main::[email protected] (line 15) 

    0x00007f42307d9d72: 4883c430   add  rsp,30h 
    0x00007f42307d9d76: 5d     pop  rbp 
    0x00007f42307d9d77: 850583535116  test  dword ptr [7f4246cef100h],eax 
               ; {poll_return} 
    0x00007f42307d9d7d: c3     ret 

难道我理解正确的话,它的作品,因为86不能让StoreStore重新排序?如果可能的话,将需要额外的内存屏障,是吗?


EDITED优秀@尤金的答案AFTER:

int tmp = i; // volatile load 
// [LoadStore] 
// [LoadLoad] 

在这里,我看你是什么均值很明显:every action below (after)性读(int tmp = i)不会重新排序。

// [StoreLoad] -- this one 
int tmp = i; // volatile load 
// [LoadStore] 
// [LoadLoad] 

在这里,你放多了一个障碍。它确保我们不会对int tmp = i重新排序。但是,为什么它很重要?为什么我有疑问?从我所知道的volatile load保证:

每个行动挥发性负载将不会被重新排序之前,易失性负载是可见的。

我看你写的:

需要有一个顺序一致性

但是,我不明白为什么需要顺序一致性。

+0

什么'a'?什么'V'?你的意思是'x'和'g'? – Andreas

+0

现在,'a'是'v'上的任何动作 - 例如它是一个动作:'x = 1'。 'v'是一个商店:'g = 1' – Gilgamesz

+0

JMM不是为x86或任何其他特定的体系结构而制作的,并且在加载或存储方面没有任何理由。 JVM有责任按照每个体系结构上的指令来实现JMM。 – assylias

回答

3

一些事情,第一个will be flushed to memory - 这是非常错误的。它几乎从来没有与主内存冲突 - 它通常会将StoreBuffer消耗到L1,如果您更容易理解这些概念,则可以使用缓存一致性协议来同步所有缓存之间的数据,即但是,这很好 - 只是知道这有点不同而且速度更快。

这是一个很好的问题,为什么[StoreLoad]确实存在,可能这会清理一些事情。 volatile确实是围栏。下面是会发生什么一个例子:

int tmp = i; // volatile load 
    // [LoadStore] 
    // [LoadLoad] 

说了volatile load发生的事情及以下是易失性存储器会发生什么:

// [StoreStore] 
// [LoadStore] 
i = tmp; // volatile store 

这不是它,还有更多。需要有一个sequential consistency,这就是为什么任何理智的实施将保证挥发性本身不会重新排序,从而两个围栏插入:

// [StoreLoad] -- this one 
int tmp = i; // volatile load 
// [LoadStore] 
// [LoadLoad] 

还有一此:

// [StoreStore] 
// [StoreLoad] 
i = tmp; // volatile store 
// [StoreLoad] -- and this one 

现在,事实证明,在x86 4个内存障碍中有3个是免费的 - 因为它是一个strong memory model。唯一需要执行的是StoreLoad

通常mfenceStoreLoadx86一个不错的选择,但同样的事情通过lock add保证,这就是为什么你看到它在那里。基本上StoreLoad的障碍。是的 - 你的最后一句话是正确的,对于较弱的记忆模型 - StoreStore障碍将是必需的。在一个侧面注释中,这是在通过构造函数内的final字段安全发布引用时使用的。在退出构造器后,插入两个栅栏:​​和StoreStore

编辑

假设你有这样的情况:

[StoreStore] 
[LoadStore] 
int x = i; // volatile store 

int j = 3; // plain actions 

j = x; // volatile load 
[LoadLoad] 
[LoadStore] 

基本上没有什么障碍会阻止volatile store重新排序与volatile load(即:挥发性负荷会首先执行)并且这会明显地引起问题;顺序一致性因此受到侵犯。

你有点错过了这里btw(如果我没有弄错)通过Every action after volatile load won't be reordered before volatile load is visible。重新排序不可能本身具有易失性 - 其他操作可以自由重新排序。我给大家举一个例子:

int y = 0; 
int tmp = i; // volatile load 
// [LoadStore] 
// [LoadLoad] 

int x = 3; // plain load 
y = 4; // plain store 

最后两个操作x = 3y = 4都是完全免费的重新排序,他们不能浮于挥发性以上,但通过它们本身可以重新排序。上面的例子将是完全合法的:

int y = 0; 
int tmp = i; // volatile load 
// [LoadStore] 
// [LoadLoad] 

// see how they have been inverted here... 
y = 4; // plain store 
int x = 3; // plain load 
+0

感谢您的令人印象深刻的答案。我编辑了我的帖子并询问了一个细节。 – Gilgamesz

+0

@Gilgamesz编辑 – Eugene

+0

非常感谢! :) – Gilgamesz