volatile语句的负载障碍在哪里?

Sal*_*ran 9 java assembly multithreading intel

我写了这个简单的Java程序:

package com.salil.threads;

public class IncrementClass {

    static volatile int j = 0;
    static int i = 0;

    public static void main(String args[]) {

        for(int a=0;a<1000000;a++);
        i++;
        j++;            
    }       
}
Run Code Online (Sandbox Code Playgroud)

这为i ++和j ++生成了以下反汇编代码(删除了剩余的反汇编代码):

  0x0000000002961a6c: 49ba98e8d0d507000000 mov       r10,7d5d0e898h
                                                ;   {oop(a 'java/lang/Class' = 'com/salil/threads/IncrementClass')}
  0x0000000002961a76: 41ff4274            inc       dword ptr [r10+74h]
                                                ;*if_icmpge
                                                ; - com.salil.threads.IncrementClass::main@5 (line 10)
  0x0000000002961a7a: 458b5a70            mov       r11d,dword ptr [r10+70h]
  0x0000000002961a7e: 41ffc3              inc       r11d
  0x0000000002961a81: 45895a70            mov       dword ptr [r10+70h],r11d
  0x0000000002961a85: f083042400          lock add  dword ptr [rsp],0h
                                                ;*putstatic j
                                                ; - com.salil.threads.IncrementClass::main@27 (line 14)
Run Code Online (Sandbox Code Playgroud)

这是我对以下汇编代码的理解:

  • mov r10,7d5d0e898h:将指针移动到IncrementClass.class以注册r10
  • inc dword ptr [r10 + 74h]:递增[r10 + 74h]处地址的4字节值,(即i)
  • mov r11d,dword ptr [r10 + 70h]:将地址[r10 + 70h]的4值移动到寄存器r11d(即将j移动到r11d)
  • inc r11d:增加r11d
  • mov dword ptr [r10 + 70h],r11d:将r11d的值写入[r10 + 70h]所以它对其他线程可见-lock add dword ptr [rsp],0h:锁定堆栈指针rsp所代表的内存地址添加0到它.

JMM声明在每次易失性读取之前必须有一个加载内存屏障,并且在每次易失性写入之后必须存在存储障碍.我的问题是:

  1. 为什么在将j读入r11d之前没有负载障碍?
  2. 如何锁定并添加到rsp确保r11d中j的值被传回主存储器.我从intel规范中读到的是,在操作期间,lock为cpu提供了对指定内存地址的独占锁定.

ver*_*tas 6

英特尔处理器x86具有强大的内存模型.

因此,所有屏障StoreStore,LoadLoad,LoadStore都是x86上的no-op. StoreLoad除外,可以通过 mfence或cpuid或锁定insn实现.您已经可以使用汇编代码确认.其他障碍只是意味着限制编译器优化和转换,因此它们不会破坏java内存模型规范.

当你在intel处理器上运行时,我假设它的x86.

请阅读

  1. http://gee.cs.oswego.edu/dl/jmm/cookbook.html供参考.

  2. http://psy-lob-saw.blogspot.com/2013/08/memory-barriers-are-not-free.html

  3. http://jsr166-concurrency.10961.n7.nabble.com/x86-NOOP-memory-barriers-td9991.html

Lock不是指令,而是指令前缀(表现为storeLoad屏障).

  1. "锁定"指令在x86汇编中意味着什么?
  2. 为什么我们需要在CMPXCHG之前使用锁前缀


Ale*_*Suo 0

volatileJava中的关键字仅保证跳过线程本地副本和缓存,并且直接从主内存加载值或写入主内存。但它不包含锁定机制。因此,从 读取volatile或写入volatile是原子的,但是一系列读取和写入操作,如上面的

j++

不是原子的,因为其他一些线程可以修改j主内存中变量的读取和写入之间的值。要实现原子增量,您需要使用封装在 java 中的原子类中的 CAS 操作,例如AtomicInteger等。或者,如果您喜欢低级编程,您可以在Unsafe类 EgUnsafe.compareAndSwapInt等中使用原子方法。

  • “Java 中的 volatile 关键字仅保证跳过线程本地副本和缓存,并且直接从主内存加载值或写入主内存” - jmm 没有这样说,并且没有生产级 JVM 实现 volatile像那样。x86 上没有明显障碍的原因是所使用的指令已经提供了必要的可见性保证。 (2认同)
  • JMM 只讨论可见性保证和重新排序限制,但不关心这些是如何实现的。在具有强大内存模型和缓存一致性的 x86 上,写回内存是绝对没有必要的,这会产生可怕的性能,并且通常不会这样做。Java 的 volatile 和 C 的 volatile 除了名称之外也没有任何共同点(C 的版本对于多线程编程完全没有用处,而 Java 的版本对 ISR 或硬件没有帮助)。请参阅 [JSR-113 食谱](http://gee.cs.oswego.edu/dl/jmm/cookbook.html) 了解其一般实现方式的信息。 (2认同)