为什么HotSpot会使用提升来优化以下代码?

23
在《Effective Java》一书中,作者提到:
while (!done) i++;

可以通过HotSpot进行优化,变成

if (!done) {
    while (true) i++;
}


我对此非常困惑。变量done通常不是const,为什么编译器能够进行优化?

4个回答

29
作者在这里假设变量done是一个局部变量,在Java内存模型中没有任何要求将其值暴露给其他线程,除非使用同步原语。或者说:变量done的值不会被任何代码更改或查看,除了此处所示的代码。

在这种情况下,由于循环不会更改done的值,因此它的值可以被有效忽略,并且编译器可以将该变量的评估提升到循环外部,防止其在循环的“热”部分进行评估。这使得循环运行速度更快,因为它需要做的工作更少。

这也适用于更复杂的表达式,例如数组的长度:

int[] array = new int[10000];
for (int i = 0; i < array.length; ++i) {
    array[i] = Random.nextInt();
}
在这种情况下,朴素的实现会对数组的长度进行10000次求值。但是,由于变量array从未被赋值且数组的长度永远不会改变,因此求值可以改为:
int[] array = new int[10000];
for (int i = 0, $l = array.length; i < $l; ++i) {
    array[i] = Random.nextInt();
}

这里还有其他与提升无关的优化方法。

希望这能帮到你。


1
我不明白,如果条件始终为真,循环如何终止? - user468311
4
我认为EJ的作者想表达的是,由于JIT编译器可以进行这种优化,程序员不应该依赖于“done”的值会从另一个线程中发生改变。JIT编译器有可能会对代码进行此类构建的优化,那么循环就永远不会终止。 - ahawtho
7
只要done不是易失性变量,即使它是一个实例或类变量,这种优化也可以发生。 - assylias

3
Joshua Bloch的《Effective Java》解释了为什么在线程之间共享变量时必须小心。如果在线程之间不存在任何明确的“先于关系”,那么HotSpot编译器允许为了速度而优化代码,正如dmide所示。
现今大多数微处理器都提供了不同种类的乱序策略。这导致了一个弱一致性模型,也是Java平台内存模型的基础。其理念是,只要程序员没有明确表达需要进行线程间协调,处理器和编译器就可以进行不同的优化。
两个关键字volatile(原子性和可见性)和synchronized(原子性、可见性和互斥)用于表达更改的可见性(对其他线程)。但是,此外您还必须了解“先于规则”(请参见Goetz等人的《Java并发实践》第341f页(JCP)和Java语言规范§17)。
那么,当调用System.out.println()时会发生什么?请参见上文。首先,您需要两个System.out.println()调用。一个在主方法中(在更改done之后),另一个在启动的线程中(在while循环中)。现在,我们必须考虑JLS §17中的程序顺序规则监视器锁定规则。这里是简短版本:您有一个公共锁对象M。在线程A解锁M之前发生的所有事情对于在B锁定M的那一刻另一个线程B是可见的(请参见JCP)。
在我们的例子中,两个线程共享System.out中的一个公共PrintStream对象。当我们查看println()内部时,您会看到调用了synchronized(this)
结论:两个线程共享一个被锁定和解锁的公共锁M。 System.out.println()“刷新”变量done的状态更改。

好的答案。注意:变量不会被清空。缓存始终是一致的,并且是真相的来源。此外,没有将“缓存刷新”到主内存的操作。 - pveentjer
你是对的,动词flush是具有误导性的。我使用双引号“flush”来强调程序员看到的内容,而不是JMM内部发生的事情。 JSR133 FAQ(https://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#volatile)提到,写入一个volatile变量会启动一个真正的刷新操作。然而,JLS对此保持沉默。原因很简单。JMM可以在每个支持的平台上实现适当的可见性保证解决方案。 - C. Grewe

1
如果您在while循环中添加System.out.println("i = " + i);,那么变量提升将不起作用,这意味着程序会按预期停止。println方法是线程安全的,因此jvm无法优化代码段?

0
public class StopThread {
private static boolean stopRequested;

private static synchronized void requestStop() {
    stopRequested = true;
}

private static synchronized boolean stopRequested() {
    return stopRequested;
}

public static void main(String[] args)
                throws InterruptedException {
    Thread backgroundThread = new Thread(new Runnable() {
        public void run() {
            int i = 0;
            while (!stopRequested())
                i++;
        }
    });
    backgroundThread.start();
    TimeUnit.SECONDS.sleep(1);
    requestStop();
}
}

上述代码在有效代码中是正确的,它等同于使用volatile修饰stopRequested

private static boolean stopRequested() {
  return stopRequested;
}

如果这个方法省略了 "synchronized" 关键字,那么程序将无法正常工作。
我认为当这个方法省略了 "synchronized" 关键字时,会导致 "hoisting"。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接