这演示了某些分配的重新排序,在 1M 次迭代中,通常有几行打印。
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 的程序集会得到这个输出(等等)
; {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 和 c 的交换分配(c 紧跟在 a 之后)。
编辑:
因为写入按 a、b、c 的顺序发生,而读取按相反的顺序 c、b、a 发生,除非对写入(或读取)重新排序,否则您永远不会看到无效状态。
单个 cpu(或内核)执行的写入对所有处理器都以相同的顺序可见,请参见例如这个答案,它指向英特尔系统编程指南第 3 卷第 8.2.2 节。
所有处理器以相同的顺序观察单个处理器的写入。