Java volatile语句的负载屏障在哪里?

Java volatile语句的负载屏障在哪里?,java,multithreading,assembly,intel,Java,Multithreading,Assembly,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++;

我编写了这个简单的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++;            
    }       
}
以下是我对以下汇编代码的理解:

  • 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指出,在每次易失性读取之前,必须有一个加载内存屏障,在每次易失性写入之后,必须有一个存储屏障。我的问题是:

  • 为什么在将j读入r11d之前没有负载屏障?
  • 锁和添加到rsp如何确保r11d中的j值被分配回主存。我从英特尔规范中读到的只是,在操作期间,锁为cpu提供了指定内存地址的独占锁

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

    j++

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

    英特尔处理器x86具有。 因此,所有barrier Store、LoadLoad、LoadStore在x86上都没有操作。 除可通过mfence或cpuid或锁定insn实现的存储加载外。 您已经可以用您的汇编代码确认。其他障碍只意味着限制编译器的优化和转换,使它们不会被打破

    在英特尔处理器上运行时,我假设它是x86

    请阅读

  • 供参考

  • Lock不是一条指令,而是一个指令前缀(表现为存储加载屏障)


  • 由于您的程序是单线程的(只有一个线程是主线程),因此JIT编译器可能会优化该障碍,就像可以优化单线程环境下的锁一样。
    此优化独立于处理器体系结构。

    关于“线程本地副本和缓存”的论点得到了支持。将原始含义追溯到
    C
    语言和中断处理程序也可以解释
    Java
    语言中借用和实现的语义Java中的volatile关键字只能保证跳过线程本地副本和缓存,并直接从主存加载值或写入主存“-jmm没有这样说,也没有生产级JVM实现那样的易失性。x86上没有明显的障碍的原因是所使用的指令已经提供了必要的可见性保证。@Voo也许我的术语和描述不准确,但这是相同的语义。JMM只讨论可见性保证和重新排序限制,但不关心如何实现这些限制。在具有强大内存模型和缓存一致性的x86上,回写到内存是绝对不必要的,会有可怕的性能,通常不会这样做。Java的volatile和C的volatile除了名称之外没有任何共同之处(C的版本对于多线程编程完全没有用处,而Java的版本对于ISR或硬件没有帮助)。请参阅以了解它通常是如何实现的。@Salil错误的假设,缓存没有被绕过,您可以非常确定该值没有被写回内存(与没有人使用直写缓存的原因相同)。
    LOCK
    之所以能为您提供必要的障碍,是因为Intel以这种方式指定了ISA(有很多种方式可以这样做,比如窥探)。在发布之前,我已经阅读了上面的大部分文章。为什么I++不是原子或线程安全的。如果您查看递增i“inc dword ptr[r10+74h]”的指令,它应该直接写入内存,并且每隔一个线程都应该能够看到这个值。据我所知,当CPU写入内存时,这个值会缓存在缓存线中,不会一直写到内存,因此需要显式指令才能写入内存。我相信这是LOCK语句,但堆栈指针上的锁如何确保缓存中的值写入内存。请看一下这个ans,x86汇编中的“LOCK”指令是什么意思?。这很清楚。Inc指令本身并不是一个读-修改-写操作,它只是增加了寄存器中的值,而如果以lock作为前缀,则肯定是这样。但编译器编写者通过inst lock加0值实现了StoreLoad屏障,从而达到了该效果。阅读ans,它非常清晰。我认为,以LOCK语句为前缀的any指令起到了存储加载屏障的作用,因为它阻止了存储之前加载的重新排序。x86的缓存一致性机制负责所有CPU都能看到已写入内存的值。我的理解正确吗?是的,请参阅C++类似的映射
      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)