如何演示Java指令重新排序问题? [英] How to demonstrate Java instruction reordering problems?
问题描述
通过Java指令重新排序,JVM会在编译时或运行时更改代码的执行顺序,这可能导致无关的语句被无序执行。
With Java instruction reordering the execution order of the code is changed by the JVM at compile time or run time, possibly causing unrelated statements to be executed out-of-order.
所以我的问题是:
有人可以提供一个示例Java程序/代码片段,该示例可靠地显示指令重新排序问题,该问题也不是由其他同步问题引起的(例如缓存/可见性或非原子读/写,例如我在我先前的问题)
Can someone provide an example Java program/snippet, that reliably shows an instruction reordering problem, that is not caused also by other synchronization issues ( such as caching/visibility or non-atomic r/w, as in my failed attempt at such a demo in my previous question )
要强调的是,我不是在寻找理论上的重新排序问题的示例。我正在寻找的是一种通过查看正在运行的程序的错误或意外结果来实际演示它们的方法。
To emphasize, I am not looking for examples of theoretical reordering issues. What I am looking for is a way to actually demonstrate them by seeing incorrect or unexpected results of a running program.
除非出现错误的行为示例,否则仅显示实际的重新排序发生在一个简单程序的汇编也可能很好。
Barring a faulty behavior example, just showing actual reordering happening in the assembly of a simple program could also be nice.
推荐答案
这演示了某些分配的重新排序,在1M迭代中,通常会有几行打印行。
This demonstrates reordering of certain assignments, out of 1M iterations there is usually couple of printed lines.
public class App {
public static void main(String[] args) {
for (int i = 0; i < 1000_000; i++) {
final State state = new State();
// a = 0, b = 0, c = 0
// Write values
new Thread(() -> {
state.a = 1;
// a = 1, b = 0, c = 0
state.b = 1;
// a = 1, b = 1, c = 0
state.c = state.a + 1;
// a = 1, b = 1, c = 2
}).start();
// Read values - this should never happen, right?
new Thread(() -> {
// copy in reverse order so if we see some invalid state we know this is caused by reordering and not by a race condition in reads/writes
// we don't know if the reordered statements are the writes or reads (we will se it is writes later)
int tmpC = state.c;
int tmpB = state.b;
int tmpA = state.a;
if (tmpB == 1 && tmpA == 0) {
System.out.println("Hey wtf!! b == 1 && a == 0");
}
if (tmpC == 2 && tmpB == 0) {
System.out.println("Hey wtf!! c == 2 && b == 0");
}
if (tmpC == 2 && tmpA == 0) {
System.out.println("Hey wtf!! c == 2 && a == 0");
}
}).start();
}
System.out.println("done");
}
static class State {
int a = 0;
int b = 0;
int c = 0;
}
}
打印用于写lambda的程序集将得到此输出(
Printing the assembly for the write lambda gets this output (among other..)
; {metadata('com/example/App$$Lambda$1')}
0x00007f73b51a0100: 752b jne 7f73b51a012dh
;*invokeinterface run
; - java.lang.Thread::run@11 (line 748)
0x00007f73b51a0102: 458b530c mov r10d,dword ptr [r11+0ch]
;*getfield arg$1
; - com.example.App$$Lambda$1/1831932724::run@1
; - java.lang.Thread::run@-1 (line 747)
0x00007f73b51a0106: 43c744d41402000000 mov dword ptr [r12+r10*8+14h],2h
;*putfield c
; - com.example.App::lambda$main$0@17 (line 18)
; - com.example.App$$Lambda$1/1831932724::run@4
; - java.lang.Thread::run@-1 (line 747)
; implicit exception: dispatches to 0x00007f73b51a01b5
0x00007f73b51a010f: 43c744d40c01000000 mov dword ptr [r12+r10*8+0ch],1h
;*putfield a
; - com.example.App::lambda$main$0@2 (line 14)
; - com.example.App$$Lambda$1/1831932724::run@4
; - java.lang.Thread::run@-1 (line 747)
0x00007f73b51a0118: 43c744d41001000000 mov dword ptr [r12+r10*8+10h],1h
;*synchronization entry
; - java.lang.Thread::run@-1 (line 747)
0x00007f73b51a0121: 4883c420 add rsp,20h
0x00007f73b51a0125: 5d pop rbp
0x00007f73b51a0126: 8505d41eb016 test dword ptr [7f73cbca2000h],eax
; {poll_return}
0x00007f73b51a012c: c3 ret
0x00007f73b51a012d: 4181f885f900f8 cmp r8d,0f800f985h
我不是确定为什么最后一个 mov dword ptr [r12 + r10 * 8 + 10h],1h
没有用putfield b和第16行标记,但是您可以看到b的互换分配
I am not sure why the last mov dword ptr [r12+r10*8+10h],1h
is not marked with putfield b and line 16, but you can see the swapped assignment of b and c (c right after a).
EDIT:
因为写以a,b,c顺序发生,而读以相反的顺序c ,b,a,除非重新定义了写入(或读取)的顺序,否则永远都不会看到无效状态。
Because writes happen in order a,b,c and reads happen in reverse order c,b,a you should never see an invalid state unless the writes (or reads) are reordered.
由单个cpu(或内核)执行的写入操作在所有处理器中都是相同的,见例如此答案,它指向《英特尔系统编程指南》 第3卷第8.2.2节。
Writes performed by single cpu (or core) are visible in same order by all processors, see e.g. this answer, which points to Intel System Programming Guide Volume 3 section 8.2.2.
所有处理器以相同的顺序观察单个处理器的写入。
Writes by a single processor are observed in the same order by all processors.
这篇关于如何演示Java指令重新排序问题?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!