Java编译器重新排序

Java编译器重新排序,java,multithreading,Java,Multithreading,今天我读了java面试问题,我读了这个问题: 问题:考虑下面的java代码片段,它初始化两个变量,两者都不易挥发,两个线程T1和T2正在修改这些值,如下两个都不同步: int x = 0; boolean bExit = false; Thread 1 (not synchronized) x = 1; bExit = true; Thread 2 (not synchronized) if (bExit == true) System.out.println("x=" + x); 现

今天我读了java面试问题,我读了这个问题: 问题:考虑下面的java代码片段,它初始化两个变量,两者都不易挥发,两个线程T1和T2正在修改这些值,如下两个都不同步:

int x = 0;
boolean bExit = false;

Thread 1 (not synchronized)
x = 1; 
bExit = true;

Thread 2 (not synchronized)
if (bExit == true) 
System.out.println("x=" + x);
现在告诉我们,线程2是否可以打印“x=0”

因此,答案是“是”。在解释中有“因为没有任何编译器指令,例如synchronized或volatile,bExit=true可能在编译器重新排序时出现在x=1之前。”在此之前,我不知道编译器可以在它之后的另一行之前执行一行


为什么要重新排序?如果我从不同的线程向控制台打印一些东西会怎么样?应该首先打印的行将在应该第二次打印的行之后打印(如果它们是从同一线程打印的)?这对我来说很奇怪(可能是因为我第一次看到这个东西是为了重新排序)。有人能解释一下吗

如果JIT编译器*不根据Java标准更改结果,它可以更改执行顺序。转换

x = 1;
bExit = true;

不会更改结果,因为没有同步,即根据标准,执行此操作时,其他线程不应读取这些变量,并且这些语句都不需要其他变量。(在现代CPU上,两个命令实际上将同时执行,这当然意味着未指定将首先更改哪个命令。)

不仅重新排序会导致这种行为。可能发生的情况是,
bExit
可以在一个内存页中,而
x
可以在另一个内存页中,如果应用程序在多处理器(或多核)系统上运行,那么如果不进行同步,则可能会提交带有
bExit
的内存页(对它的更改将在所有其他内核中可见)在使用
x
的内存页之前

*编辑:Java编译器(将.Java编译成.class)不能改变线程执行顺序,但JIT编译器(将.class编译成二进制代码)可以。然而,如果Java编译器认为某些语句是多余的,则可以省略它们,例如

x = 1;
bExit = true;
x = 2;

可以优化掉
x=1

线程在随机时间运行。因此,当使用多线程时,即使在许多不同的时间运行同一个已编译的Java程序,也可能导致多个结果

代码将始终以以下内容开头:

int x = 0;
boolean bExit = false;
然后线程开始了,疯狂开始了。这些语句可以按任意顺序进行分析,也可能在任意时间暂停。X

以下是您使用的命令列表,带有编号:

  • x=1
  • bExit=true
  • 如果(bExit==true)
    那么,总而言之,可以打印“x=0”,因为: -编译器重新排序 -如果x为1且bExit为true,但第二个线程只看到bExit中的更改。所以bExit将为真,它将打印x的值,但它不会看到x的变化,它将打印0。 修正了总结中的错误。
    感谢大家的时间和回答。

    记住,两个线程同时运行。如果没有同步,第一个线程可以执行
    intx=0bExit==true
    ;没有重新排序,只有两个线程在执行。如果以及何时实现同步,将发生重新排序。不要假设两个线程彼此独立地执行什么操作。例如,一个无效的假设是,相同的代码在两个不同的线程中执行的时间总是相同的。我一定是误解了这个问题。从我的角度来看,
    x=0
    永远不会被打印出来。@RobertHarvey,前提是这些语句可以在单个线程中无序执行。因为要打印“x=0”,线程1的第二行需要在线程1的第一行之前执行。其他评论者说这确实是可能的;我本以为不会,但我愿意。@DavidP.Caldwell:thread1执行
    x=1主线程执行这两条语句,线程1执行
    bExit=true线程2执行这两条语句。@RobertHarvey啊,您假设主线程与其他两条同时运行。我假设这些语句是在“线程1”或“线程2”启动之前执行的,考虑到问题的措辞方式以及研究指南提问者使用的“答案”给出的原因(“编译器重新排序”,而不是“你不懂多线程”)。为什么我们会有不同的看法是有道理的,但它确实改变了结果。如果编译器没有进行这些重新排序,则保证永远不会看到
    x=0
    。但是如果它确实做出了这些改变,那么行为是未定义的。(你可能会看到,你可能不会。)在我看来,这不仅仅是一个竞争条件,而是从根本上改变了程序的行为。你基本上是正确的,@CraigOtis,编译器从来不会进行这些重新排序。保证在线程内按顺序执行。然而,硬件可以对内存写入进行重新排序,而且这种情况不可预测。因此,在写入
    x
    之前,第二个线程可能会看到
    bExit
    ==true,尽管第一个线程永远不会以相反的顺序看到它们。@CraigOtis它不会按照Java规范更改结果,因为在另一个线程中更改未同步变量时,在格式良好的程序中决不会读取未同步变量。@CraigOtis“如果编译器没有进行这些重新排序,则保证永远不会看到x=0。”不是这样。任何数量的其他事情都可能导致写入被重新排序,例如CPU中已发布的写入缓冲区。仅仅因为偶尔需要在这些操作之间进行排序,就强迫每一个碰巧发生的内存操作严格按顺序进行是没有意义的。这样做更有意义
    int x = 0;
    boolean bExit = false;