为什么此示例在并发代码中不显示可见性问题

时间:2019-09-06 15:01:19

标签: java multithreading concurrency visibility

我试图避免编写代码的所有常见陷阱,这些代码在多线程代码的并发访问中展示可见性问题。但是,被测试的变量始终显示更新后的值。它的行为就像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++;
    }
}

更新

我的问题不是关于它是否不可能发生,我毫不怀疑它会发生。我的问题是如何使其更可靠地发生。据我所知,我的代码应该显示可见性问题,如果不是,那么为什么呢?

自从我发布此问题以来,我的脑海中弹出了至少两个可能的原因:

  • HotSpot编译器启动;循环最终确定为热代码。编译的副作用可能是变量被同步(由于编译,可能仅通过将它们放到主存储器中)。 -可以通过预热代码来解决此问题
  • 在线程1中写入A到在线程2中读取它之间的时滞足够长,OS可以将执行与另一个进程(可能是某些系统服务)交织;诸如此类的上下文切换会导致大量的缓存失效,从而导致必须从主内存中重新读取变量,从而导致A在线程2中具有更新的值。-只有通过将A写入更接近从A读取,发现理想的自旋数将非常善变

还有其他想法吗?

0 个答案:

没有答案