2

On my machine the following code runs indefinitely (Java 1.7.0_07):

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);
    stopRequested = true;
}

However, add a single lock object and a single synchronized statement NOT around stopRequested (in fact, nothing occurs in the synchronized block), and it terminates:

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

In the original code, the variable stopRequested is "hoisted", becoming:

if (!stopRequested)
    while (true)
        i++;

However, in the modified version, it seems this optimization is not occurring, why? (In fact, why is synchronized not optimized away entirely?)

4

2 に答える 2

3

VMは、ロックが他のスレッドによって同期されていないことを推論できないため、最適化することはできません。

Javaメモリモデルごとに、すべての同期ブロックは完全に順序付けられており、この順序(同じロック上)は、発生前の関係を確立するのに役立ちます。そのため、VMは同期ブロックを削除できません。1つのスレッドのみがオブジェクトで同期していることをVMが証明できない限り、これらの同期ブロックはすべて、発生前の関係に影響を与えることなく削除できます。

ロックがローカルオブジェクトの場合、VMはエスケープ分析を実行してロックを解除できます。私たちは何年もの間エスケープ分析について聞いてきましたが、例が示すように、そして私が少し前にテストしたように、それはまだ機能していないようです。

ロックの省略が行われていない理由があるかもしれません。最適化は、ローカルなどを使用するコードに最適ですVectorStringBufferただし、これは古いコードにのみ当てはまります。長い間誰もそれをしません。

一部のコードは、より強力なJava5より前のモデルに依存している場合もあります。このモデルでは、ロックを完全に排除することはできません。OPの細工された例と同様に、新しいモデルでは正しくないが、過去何年も機能しているプログラムが多数ある可能性があります。ロックエリジオンはこれらのプログラムを壊す可能性があります。

于 2013-01-15T04:46:45.003 に答える
2

これはメモリの可視性の問題のように見えるかもしれませんが、通常、単純な例では JIT 最適化の問題です。これは、そのスレッドでフラグを変更しているかどうかを JIT が検出できるためです。変更していない場合はインライン化できます。それを効果的に無限ループに変えます。

これを判断できる 1 つの方法は、可視性の問題は短命であり、通常は短すぎて見えないということです。それらはランダムですが、通常は 1 マイクロ秒から 1 ミリ秒です。つまり、スレッド コンテキストが切り替わるまで、そして再度実行されると、古い値は維持されません。変更を「検出」しない無限ループに一貫して変わった例を見ることができるという事実は、明らかです。

を使用してループを遅くすると、Thread.sleep(10)コンパイルに十分な時間実行できなくなる可能性があります。最適化するには、10,000回以上ループする必要があります。これは通常、問題を「修正」します。

volatile 変数の使用や同期ブロックの追加などのスレッド セーフ コードを追加すると、最適化が行われなくなる可能性があります。

于 2013-01-15T08:22:35.440 に答える