1

私たちは皆、Java がコードを徹底的に最適化することを知っており、Java を気に入っています。まあ、ほとんどの場合。以下は、私の頭を本当に混乱させるコードです。

public class BrokenOptimizationTest {

/**
 * This thread constantly polls another thread object's private field.
 */
public static class ComparingThread extends Thread {
    private int currentValue = 0;
    private AdditionThread otherThread = null;

    public ComparingThread(AdditionThread add) {
        this.otherThread = add;
    }

    @Override
    public void run() {
        while (true) {
            int testValue = currentValue;

            if (BrokenOptimizationTest.shouldDoSomething) {

                do {
                    testValue = otherThread.getValue();
                    BrokenOptimizationTest.doSomething();
                    // System.out.println(testValue); // to see testValue really changes
                }
                while (testValue == currentValue);

            }
            else {

                do {
                    testValue = otherThread.getValue();
                    // System.out.println(testValue); // to see testValue really changes
                }
                while (testValue == currentValue);

            }

            System.out.println("{ testValue: " + testValue + ", currentValue: " + currentValue + " }");

            currentValue = testValue;
        }
    }
}

/**
 * This thread often adds to its pollable value.
 */
public static class AdditionThread extends Thread {
    private int currentValue = 0;
    public long queryCount = 0;

    public int getValue() {
        ++queryCount;
        return currentValue;
    }

    @Override
    public void run() {
        while (true) {
            ++currentValue;

            //I said 'often', so sleep some more
            try {
                Thread.sleep(1);
            }
            catch (InterruptedException e) {}
        }
    }
}

/**
 * Whether or not the program must simulate doing an expensive calculation between consecutive queries.
 */
public static boolean shouldDoSomething = false;

/**
 * Simulates doing an expensive calculation
 */
public static void doSomething() {
    try {
        Thread.sleep(0, 100);
    }
    catch (InterruptedException e) {}
}


/**
 * Call the program with something like "slow" to enable doSomething
 */
public static void main(String[] args) {
    if (args.length >= 1 && (args[0].toLowerCase().contains("slow") || args[0].toLowerCase().contains("dosomething")))
        shouldDoSomething = true;


    AdditionThread addThread = new AdditionThread();
    ComparingThread compThread = new ComparingThread(addThread);
    addThread.start();
    compThread.start();

    /**
     * Print the current program state every now and then.
     */
    while (true) {
        System.out.println("{ currentValue: " + addThread.getValue() + ", activeThreads: " + Thread.activeCount() + ", queryCount: " + addThread.queryCount + " }");
        System.out.flush();

        try {
            Thread.sleep(1000);
        }
        catch (InterruptedException e) {}
    }
}
}

結果は、高速、低速のシングルスレッド プロセッサとマルチスレッド プロセッサの間で異なる場合があります。テストしたコンピューター (doSomething なし) では、出力は次のようになります。

{ currentValue: 1, activeThreads: 3, queryCount: 1 }
{ testValue: 1, currentValue: 0 }
{ testValue: 2, currentValue: 1 }
{ testValue: 3, currentValue: 2 }
{ testValue: 4, currentValue: 3 }
{ testValue: 5, currentValue: 4 }
{ testValue: 6, currentValue: 5 }
{ testValue: 7, currentValue: 6 }
{ testValue: 8, currentValue: 7 }
{ testValue: 9, currentValue: 8 }
{ testValue: 10, currentValue: 9 }
{ testValue: 11, currentValue: 10 }
{ testValue: 12, currentValue: 11 }
{ testValue: 13, currentValue: 12 }
{ currentValue: 994, activeThreads: 3, queryCount: 2176924819 }
{ currentValue: 1987, activeThreads: 3, queryCount: 4333727079 }
{ currentValue: 2980, activeThreads: 3, queryCount: 6530688815 }
{ currentValue: 3971, activeThreads: 3, queryCount: 8723797559 }

CompareThreadの最初の数回の反復は正常に機能し、その後 Java が「最適化」します。testValuecurrentValueは常に等しく、スレッドが最も内側のループを離れることはありませんが、それらの値を変更し続けます。私が考えることができる唯一の原因は、次のように Java が順不同で実行されることです。

do {
    testValue = otherThread.getValue();
    currentValue = testValue; // moved up from beneath the loop
}
while (testValue == currentValue);

パフォーマンスを向上させることができるため、Java コンパイラで順不同の実行が許可されていることは理解していますが、これらのステートメントは明らかに相互に依存しています。

私の質問は単純です:なぜですか?Java がこのようにプログラムを実行するのはなぜですか?

注: プログラムがパラメーター doSomething で開始された場合、または AdditionThread.currentValue がvolatileにされた場合、コードは正常に実行されます。

4

1 に答える 1

4

あなたはあなた自身の質問に答えました:

AdditionThread.currentValue を volatile にすると、コードは問題なく実行されます。

Java メモリ モデルでは、ComparingThread 内から AdditionThread.currentValue を読み取ったときに、AdditionThread に存在する最新バージョンが表示されるという保証はありません。データが他のスレッドから見えるように意図されている場合は、提供されている volatile、synchronized、java.util.concurrent.* のいずれかのツールを使用して、可視性の保証が必要であることをシステムに伝える必要があります。

順不同の実行は、予期しない動作を引き起こす最適化ではありません。ComparingThread が独自のスタックに AdditionThread.currentValue のコピーを保持しているだけです。

「doSomething」をオンにすると、スレッドをスリープ状態にすると、通常、スレッドが復帰したときにスタックがリフレッシュされるため、修正されますが、これは正式には保証されていません。

于 2013-05-07T20:02:52.210 に答える