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)?考虑到它已经是原子的.
即使i ++转换为X86 Fetch-And-Add指令也不会改变,因为Fetch-And-Add指令中提到的内存是指CPU的本地内存注册而不是设备/应用程序的一般内存. .在现代CPU上,此属性将扩展到CPU的本地内存缓存,甚至可以扩展到多核CPU的不同内核使用的各种缓存,但是在多线程应用程序的情况下; 绝对没有保证这个发行版将扩展到线程本身使用的内存副本.
显然,在多线程应用程序中,如果一个变量可以被同时运行的不同线程修改,那么你必须使用系统提供的一些同步机制,你不能依赖于指令i ++占用一行java的事实.代码是原子的.
考虑第二个问题。
您暗示这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 <- 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++。