考虑以下简单的Java应用程序:

public class Main {
    public int a;
    public volatile int b;

    public void thread1(){
        int b;
        a = 1;
        b = this.b;
    }

    public void thread2(){
        int a;
        b = 1;
        a = this.a;
    }

    public static void main(String[] args) throws Exception {
        Main m = new Main();
        while(true){
            m.a = 0;
            m.b = 0;
            Thread t1 = new Thread(() -> m.thread1());
            Thread t2 = new Thread(() -> m.thread2());
            t1.start();
            t2.start();
            t1.join();
            t2.join();
        }
    }
}

QUESTION:读入局部变量是否可能得到thread1::b = 0thread2::a = 0

从JMM的Angular 来看,我无法证明这是不可能发生的,所以我开始分析x86-64的编译代码.

以下是编译器为方法thread1thread2生成的结果(为简单起见,省略了与While循环无关的代码和由-XX:+PrintAssembly生成的一些注释):

thread1:

  0x00007fb030dca235: movl    $0x1,0xc(%rsi)    ;*putfield a
  0x00007fb030dca23c: mov     0x10(%rsi),%esi   ;*getfield b

thread2:

  0x00007fb030dcc1b4: mov     $0x1,%edi
  0x00007fb030dcc1b9: mov     %edi,0x10(%rsi)
  0x00007fb030dcc1bc: lock addl $0x0,0xffffffffffffffc0(%rsp) ;*putfield b 
  0x00007fb030dcc1c2: mov     0xc(%rsi),%esi    ;*getfield a

因此,我们这里的情况是,volatile次读取是免费完成的,volatile次写入之后需要mfence次(或lock add次).

所以thread1的存储在加载之后仍然可以被转发,因此thread1::b = 0thread2::a = 0是可能的.

推荐答案

是的,你的分析看起来是对的.这是StoreLoad试金石测试,只有one个侧面具有StoreLoad屏障(如C++std::atomic iwth memory_order_seq_cst或Java volatile).在这两个方面都需要它来消除这种可能性.关于双方都没有这样的障碍的情况,请参见杰夫·普雷辛格的Memory Reordering Caught in the Act篇文章.

a=1b=this.b的StoreLoad重新排序允许有效排序

   thread1        thread2
                  b=this.b        // reads 0
    b=1
    a=this.a                      // reads 0
                  a=1

(这种混乱的名称就是为什么对于示例和重新排序的石蕊测试来说, Select 像r0r1这样的名称作为"寄存器"来谈论线程观察到的加载结果是很正常的,这肯定不是与共享变量相同的名称,这使得语句的含义与上下文相关,并且在重新排序的关系图中查看和思考是一件很痛苦的事情.)

所以线程1的存储在加载后仍然可以是forwarded,因此thread1::b = 0thread2::a = 0是可能的.

你的意思似乎是"之后重新订购",而不是转发.在内存排序上下文中的"转发"将意味着存储到加载的转发(其中加载在数据变得全局可见之前从存储缓冲区拉出数据,以便它立即看到自己的存储,其顺序与其他线程不同).但是您的两个线程都没有重新加载自己的存储,所以这不会发生.

X86的内存模型基本上是程序顺序+具有存储到加载转发的存储缓冲区,因此StoreLoad重新排序是唯一可能发生的类型.

所以,是的,这是你最接近排除ra=rb=0的可能性,同时仍然为它的发生打开了一扇窗.在强序ISA(X86)上运行,一侧有屏障.

当您在每个线程启动时只进行一次测试时,也不太可能观察到这一点;毫不奇怪,您花了30分钟才在足够接近的时间跨内核执行来观察这一点.(测试速度更快不是小事,就像第三个线程在测试之间重置并唤醒其他两个线程一样? 但是,做一些事情使两个线程更有可能同时到达这个代码可能会有很大帮助,比如让它们都旋转等待相同的变量,这样它们可能会在彼此的一百个周期内唤醒.)

Java相关问答推荐

内容处置 destruct 了PSP请求

我可以在regex中的字符类中放置断言吗?

具有额外列的Hibert多对多关系在添加关系时返回NonUniqueHealthExcellent

Cucumber TestNG Assert失败,出现java. lang. Numbercycle异常

Junit with Mockito for java

是否保证在事务性块的末尾标记违反约束?

Java FX中的河内之塔游戏-在游戏完全解决之前什么都不会显示

AssertJ Java:多条件断言

在运行MVN测试时,为什么构建失败,并显示了java.lang.ClassNotFoundException:java.net.http.HttpResponse?

获取字符串中带空格的数字和Java中的字符

在Spring Boot应用程序中导致";MediaTypeNotSupportdException&qot;的映像上载

Domino Designer 14中的保存代理添加了重影库

如何将Java文档配置为在指定的项目根目录中生成?

有没有可能在时间范围内得到多种解决方案?

舰队运行配置Maven版本

STREAMS减少部分结果的问题

如何在IntelliJ IDEA的Build.sbt中添加外部JAR文件?

在java中使用不同的application.properties-jar而不使用Spring

无法使用Open WebStart Java 8运行jnlp

使用@ExceptionHandler的GlobalExceptionHandler还是来自服务器的REST应答的ResponseEntity?