我试图避免编写代码的所有常见陷阱,这些代码在多线程代码的并发访问中展示可见性问题。但是,被测试的变量始终显示更新后的值。它的行为就像volatile
。
我避免了所有发生的原因,避免了像CountDownLatch
之类的所有同步工具,不使用Thread.sleep()
甚至是System.out.println()
,因为它们可能都已经被隐藏了同步在此之前触发发生后果。
唯一的发生动作是更新线程的开始,但这发生在测试之前,因此不应干扰测试。
我很好奇。我可能还忽略了什么?
是什么促使JMV同步测试的变量?
这是代码:
int a = 0;
@Test
public void testVisibility() {
new Thread(() -> {
// First thread:
// write to A new value as soon as possible; second thread should be still
// idly spinning at this time
a = 100;
// delay ending the thread until after the test is done,
// ending thread has "happens before" implications we want to avoid here
idle(500_000_000, dummy1);
// this is never printed because testing framework interrupts it while
// still idling; this line is never reached.
System.out.println("thread finished");
}).start();
// Second thread:
// For this I reuse main test thread; JUnit assertions has to be done in the main tests thread
// otherwise they are not (normally) propagated up making this test to always pass.
// Ensure that following code is ensured to be run after a is set to 100
// we cannot use sleep() as it may cause "happens before" internally
// It must be large number, as starting thread is a relatively slow process,
// much slower than my dummy loop, on my hardware it must be > 1_000
idle(10_000, dummy2);
// It's expected to see stale value of 0 here, because it is not volatile and was modified in a different thread
// Yet it always see the updated value :(
int a_ = a;
assertEquals(100, a_);
// it's safe to use println() now, after the test
System.out.println(dummy1); // usually prints number a bit above 10_000
System.out.println(dummy2); // usually prints number a bit above 10_000
}
private long dummy1 = 0;
private long dummy2 = 0;
private static void idle(long lengthOfDelayInNumberOfSpins, long dummy) {
for (int i=0; i < lengthOfDelayInNumberOfSpins; i++) {
dummy++;
}
}
更新
我的问题不是关于它是否不可能发生,我毫不怀疑它会发生。我的问题是如何使其更可靠地发生。据我所知,我的代码应该显示可见性问题,如果不是,那么为什么呢?
自从我发布此问题以来,我的脑海中弹出了至少两个可能的原因:
还有其他想法吗?