JIT是出于这种行为的原因吗?

时间:2013-11-06 08:46:25

标签: java multithreading jit non-volatile

受此question的启发,我写了测试:

public class Main {

    private static final long TEST_NUMBERS = 5L;

    private static final long ITERATION_NUMBER = 100000L;

    private static long value;

    public static void main(final String [] args) throws Throwable {
        for(int i=0; i<TEST_NUMBERS; i++) {
            value = 0;
            final Thread incrementor = new Thread(new Incrementor());
            final Thread checker = new Thread(new Checker());
            incrementer.start();
            checker.start();
            checker.join();
            incrementer.join();
        }
    }

    static class Incrementor implements Runnable {
        public void run() {
            for(int i=0; i<ITERATION_NUMBER; i++){
                ++value;
            }
        }
    }

    static class Checker implements Runnable {
        public void run() {
            long nonEqualsCount = 0;
            for(int i=0; i<ITERATION_NUMBER; i++){
                if(value != value) {
                    ++nonEqualsCount;
                }
            }
            System.out.println("nonEqualsCount = " + nonEqualsCount);
        }
    }
}

此程序以常见情况打印:

nonEqualsCount = 12; //or other non 0 value;
nonEqualsCount = 0;
nonEqualsCount = 0;
nonEqualsCount = 0;
nonEqualsCount = 0;

首先:我解释这种行为是JIT编译器的存在。 “热备”后每个线程的JIT编译器缓存值非volatile字段。对吗?

第二:如果是对或不对,我该如何验证呢?

P.S。 - 我知道 PrintAssebly -option。

更新:环境:Windows 7 64位,JDK 1.7.0_40-b43(热点)。

4 个答案:

答案 0 :(得分:3)

增量long变量不是原子的(64位大)。 在条件(value != value)中:可能发生在读取值value之间,第一个线程可以更改值。 volatile类型与visibility相关联。非易失性变量值可能是陈旧的。所以你的第一个结论似乎是正确的。

答案 1 :(得分:2)

你看到的可能是JIT的神器。在它开始之前,Java字节代码被解释,这意味着在比较期间检查程序线程有很多机会被中断。

此外,由于执行了更多代码,因此CPU缓存更有可能需要刷新。

当代码由JIT优化时,它可能会插入64位操作,并且由于只执行少量代码,缓存不会再刷新到主内存,这意味着线程没有机会看到另一个人做出的改变。

答案 2 :(得分:2)

在你的程序第一次通过时,这些陈述可能是正确的:

此代码可能能够证明类型++value(以及long)变量的增量操作(int)不是原子的。此外,如果未在同步块内使用,则还可以证明!=操作不是线程安全的。但这与使用的数据类型无关。

您在第一次传递后发现更改的观察结果也是正确的,但是例如,如果您使用的是Oracle / SUN JVM,则此JIT-Complier(“Hotspot Engine”)的实现取决于技术架构正在继续。

因此很难说并且验证JIT编译器是否对此负责。尝试使用这种方法推导出JIT-Complier / Hotspot引擎的实现细节是一种非常实用的研究方法。例如,从Solaris切换到Windows时,您的观察可能会有所不同。

以下是Hotspot引擎实现细节的链接: http://www.oracle.com/technetwork/java/javase/tech/index-jsp-136373.html

为了产生进一步的实证结果,您可以尝试将JVM恢复为在经典模式下运行或减少JVM中的优化量(客户端模式?)。如果行为发生变化,那么这可能是理论正确性的另一个指标。

无论如何:我很好奇你的发现是什么: - )

答案 3 :(得分:2)

虽然你认为这是由JIT引起的,但它与volatile无关。

一些 JIT在运行中进行内部优化,并删除不必要的代码以加快速度,这正是这里发生的事情。 JIT确定比较value != value始终为false并完全删除整个代码块。此外可以确定这个for循环现在是空的并且也删除了整个循环。因此,这将是最终优化的检查程序类:

public void run() {
  System.out.println("nonEqualsCount = 0");
}

您可以通过测量每次传递执行此线程所需的时间来验证这一点。在第一遍可能需要一些时间才能完成,在第二阶段,println只需要几纳秒。

注意:作为一般规则,您不能指望JIT做任何事情。根据实际实施,硬件和其他因素,它可能会或可能不会优化您的代码。如果它确实优化了,结果同样无法确定,例如代码可能在慢速硬件上比在快速硬件上更早地进行优化。