字节码指令与处理器操作之间的关系

Mik*_*378 10 java x86 bytecode processor atomicity

Java规范保证原始变量赋值总是原子的(期望long和双精度)types.

相反,对应于着名的增量操作的获取和添加操作i++将是非原子的,因为导致读 - 修改 - 写操作.

假设这段代码:

public void assign(int b) {
    int a = b;
}
Run Code Online (Sandbox Code Playgroud)

生成的字节码是:

public void assign(int);
    Code:
       0: iload_1       
       1: istore_2      
       2: return 
Run Code Online (Sandbox Code Playgroud)

因此,我们看到赋值由两个步骤组成(加载和存储).

假设这段代码:

public void assign(int b) {
        int i = b++;
}
Run Code Online (Sandbox Code Playgroud)

字节码:

public void assign(int);
    Code:
       0: iload_1       
       1: iinc          1, 1    //extra step here regarding the previous sample
       4: istore_2      
       5: return 
Run Code Online (Sandbox Code Playgroud)

知道X86处理器可以(至少是现代处理器)以原子方式操作增量操作,如上所述:

在计算机科学中,fetch-and-add CPU指令是一种特殊指令,它以原子方式修改存储器位置的内容.它用于在多处理器系统中实现互斥和并发算法,信号量的泛化.

因此,第一个问题:尽管字节码需要两个步骤(加载和存储),但是Java依赖于这样的事实:赋值操作是一个操作,无论处理器的体系结构如何都始终以原子方式执行,因此可以确保永久原子性(对于原始赋值) )在其规格?

第二个问题:用非常现代的X86处理器确认并且不跨不同架构共享编译代码是不对的,根本不需要同步i++操作(或AtomicInteger)?考虑到它已经是原子的.

Syl*_*inL 5

即使i ++转换为X86 Fetch-And-Add指令也不会改变,因为Fetch-And-Add指令中提到的内存是指CPU的本地内存注册而不是设备/应用程序的一般内存. .在现代CPU上,此属性将扩展到CPU的本地内存缓存,甚至可以扩展到多核CPU的不同内核使用的各种缓存,但是在多线程应用程序的情况下; 绝对没有保证这个发行版将扩展到线程本身使用的内存副本.

显然,在多线程应用程序中,如果一个变量可以被同时运行的不同线程修改,那么你必须使用系统提供的一些同步机制,你不能依赖于指令i ++占用一行java的事实.代码是原子的.


Shy*_*hyJ 4

考虑第二个问题

您暗示这i++将转换为 X86 Fetch-And-Add 指令,但事实并非如此。如果代码是由 JVM 编译和优化的,则可能是正确的(必须检查 JVM 的源代码来确认这一点),但该代码也可以在解释模式下运行,其中获取添加分开的并且不同步。

出于好奇,我检查了这段 Java 代码生成的汇编代码:

public class Main {
    volatile int a;

  static public final void main (String[] args) throws Exception {
    new Main ().run ();
  }

  private void run () {
      for (int i = 0; i < 1000000; i++) {
        increase ();
      }  
  } 

  private void increase () {
    a++;
  }
}
Run Code Online (Sandbox Code Playgroud)

我使用了Java HotSpot(TM) Server VM (17.0-b12-fastdebug) for windows-x86 JRE (1.6.0_20-ea-fastdebug-b02), built on Apr 1 2010 03:25:33JVM 版本(我的驱动器上有这个版本)。

这些是运行它的关键输出 ( java -server -XX:+PrintAssembly -cp . Main):

首先它被编译成这样:

00c     PUSHL  EBP
    SUB    ESP,8    # Create frame
013     MOV    EBX,[ECX + #8]   # int ! Field  VolatileMain.a
016     MEMBAR-acquire ! (empty encoding)
016     MEMBAR-release ! (empty encoding)
016     INC    EBX
017     MOV    [ECX + #8],EBX ! Field  VolatileMain.a
01a     MEMBAR-volatile (unnecessary so empty encoding)
01a     LOCK ADDL [ESP + #0], 0 ! membar_volatile
01f     ADD    ESP,8    # Destroy frame
    POPL   EBP
    TEST   PollPage,EAX ! Poll Safepoint

029     RET
Run Code Online (Sandbox Code Playgroud)

然后将其内联并编译为:

0a8   B11: #    B11 B12 &lt;- B10 B11   Loop: B11-B11 inner stride: not constant post of N161 Freq: 0.999997
0a8     MOV    EBX,[ESI]    # int ! Field  VolatileMain.a
0aa     MEMBAR-acquire ! (empty encoding)
0aa     MEMBAR-release ! (empty encoding)
0aa     INC    EDI
0ab     INC    EBX
0ac     MOV    [ESI],EBX ! Field  VolatileMain.a
0ae     MEMBAR-volatile (unnecessary so empty encoding)
0ae     LOCK ADDL [ESP + #0], 0 ! membar_volatile
0b3     CMP    EDI,#1000000
0b9     Jl,s  B11   # Loop end  P=0.500000 C=126282.000000
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,它不使用 Fetch-And-Add 指令a++