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

Java规范保证原始变量赋值始终是primefaces的(期望long和双types

相反,对应于着名的i++增量操作的Fetch-and-Add操作将是非primefaces的,因为导致读 – 修改 – 写操作。

假设这段代码:

 public void assign(int b) { int a = b; } 

生成的字节码是:

 public void assign(int); Code: 0: iload_1 1: istore_2 2: return 

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

假设这段代码:

 public void assign(int b) { int i = b++; } 

字节码:

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

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

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

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

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

考虑第二个问题

你暗示i++将转换为X86 Fetch-And-Add指令,这是不正确的。 如果代码由JVM编译和优化,则可能是真的(必须检查JVM的源代码以确认),但该代码也可以在解释模式下运行,其中fetchadd是分离的而不是同步的。

出于好奇,我检查了为这个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++; } } 

我使用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:33版本的JVM(这一个我在我的驱动器上的某个地方)。

这些是运行它的关键输出( 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 

然后它被内联并编译成:

 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 

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

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

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

关于你的第一个问题:读取和写入是primefaces的,但读/写操作不是。 我找不到关于原语的具体参考,但是JLS#17.7在引用方面有类似之处:

对引用的写入和读取始终是primefaces的,无论它们是作为32位还是64位实现。

所以在你的情况下,iload和istore都是primefaces的,但整个(iload,istore)操作不是。

[考虑]是否完全不需要同步i ++操作是不对的?

关于你的第二个问题,下面的代码在我的x86机器上打印982(而不是1,000),这表明某些++在转换中丢失了==>你需要正确地同步一个++操作,即使在支持fetch的处理器架构上也是如此 – 并添加指令。

 public class Test1 { private static int i = 0; public static void main(String args[]) throws InterruptedException { ExecutorService executor = Executors.newFixedThreadPool(10); final CountDownLatch start = new CountDownLatch(1); final Set set = new ConcurrentSkipListSet<>(); Runnable r = new Runnable() { @Override public void run() { try { start.await(); } catch (InterruptedException ignore) {} for (int j = 0; j < 100; j++) { set.add(i++); } } }; for (int j = 0; j < 10; j++) { executor.submit(r); } start.countDown(); executor.shutdown(); executor.awaitTermination(1, TimeUnit.SECONDS); System.out.println(set.size()); } }