7

以下のコードに潜在的な同時実行性の問題があるかどうかを調べようとしています。具体的には、揮発性変数に関連する可視性の問題。揮発性は次のように定義されます。この変数の値はスレッドローカルでキャッシュされることはありません。すべての読み取りと書き込みは「メインメモリ」に直接送信されます。

public static void main(String [] args)
{
    Test test = new Test();

    // This will always single threaded
    ExecutorService ex = Executors.newSingleThreadExecutor();

    for (int i=0; i<10; ++i)
        ex.execute(test);
}

private static class Test implements Runnable {
    // non volatile variable in question
    private int state = 0;

    @Override
    public void run() {
        // will we always see updated state value? Will updating state value
        // guarantee future run's see the value?
        if (this.state != -1)
            this.state++;
    }
}

上記のシングルスレッドエグゼキュータの場合:

test.stateを不揮発性にしても大丈夫ですか?言い換えると、連続するすべてのTest.run()(これも、エグゼキュータがシングルスレッドであるため、同時にではなく順次発生します)は、常に更新されたtest.state値を参照しますか?そうでない場合、Test.run()を終了すると、スレッドで行われた変更がローカルでメインメモリに書き戻されることが保証されませんか?それ以外の場合、スレッドの終了時にそうでない場合、ローカルで行われた変更がメインメモリに書き戻されるのはいつですか?

4

6 に答える 6

4

単一のスレッドである限り、それを揮発性にする必要はありません。複数のスレッドを使用する場合は、volatileを使用するだけでなく、同期する必要があります。数値の増分は不可分操作ではありません。これはよくある誤解です。

public void run() {
    synchronize (this) {
        if (this.state != -1)
            this.state++;
    }
}

同期を使用する代わりに、AtomicInteger#getAndIncrement()を使用することもできます(以前の場合はifが必要ない場合)。

private AtomicInteger state = new AtomicInteger();

public void run() {
    state.getAndIncrement()
}
于 2009-12-14T16:15:33.460 に答える
3

もともと、私はこのように考えていました:

タスクが常に同じスレッドで実行されていれば、問題はありません。ただしExcecutor、によって生成された newSingleThreadExecutor()スレッドは、何らかの理由で強制終了されたスレッドを置き換える新しいスレッドを作成する可能性があります。置換スレッドがいつ作成されるか、またはどのスレッドがそれを作成するかについての保証はありません。

スレッドがいくつかの書き込みを実行してから新しいスレッドを呼び出すとstart()、それらの書き込みは新しいスレッドに表示されます。ただし、この場合にそのルールが適用されるという保証はありません。

しかし、評判の悪いことは正しいExecutorServiceです。可視性を確保するための十分な障壁なしに正しいものを作成することは事実上不可能です。別のスレッドの死を検出することは、同期との関係であることを忘れていました。ワーカースレッドをアイドル状態にするために使用されるブロッキングメカニズムにもバリアが必要です。

于 2009-12-14T16:57:59.927 に答える
2

はい、エグゼキュータが途中でスレッドを置き換えたとしても、安全です。スレッドの開始/終了も同期ポイントです。

http://java.sun.com/docs/books/jls/third_edition/html/memory.html#17.4.4

簡単な例:

static int state;
static public void main(String... args) {
    state = 0;                   // (1)
    Thread t = new Thread() {
        public void run() {
            state = state + 1;   // (2) 
        }
    };
    t.start();
    t.join();
    System.out.println(state);  // (3)
}

(1)、(2)、(3)は適切に順序付けられ、期待どおりに動作することが保証されています。

シングルスレッドエグゼキュータの場合、「タスクは順番に実行されることが保証されています」、次のタスクを開始する前に、何らかの方法で1つのタスクの終了を検出する必要があります。これにより、異なるタスクが適切に同期されますrun()

于 2009-12-14T18:13:04.943 に答える
0

あなたのコード、特にこのビット

            if (this.state != -1)
                    this.state++;

状態値のアトミックテストと、並行コンテキストでの状態への増分が必要になります。したがって、変数が揮発性であり、複数のスレッドが関与している場合でも、並行性の問題が発生します。

ただし、設計は、Testのインスタンスは常に1つだけであり、その単一のインスタンスは単一の(同じ)スレッドにのみ付与されるという主張に基づいています。(ただし、単一のインスタンスは実際にはメインスレッドとエグゼキュータースレッドの間で共有されている状態であることに注意してください。)

これらの仮定をより明確にする必要があると思います(たとえば、コードではThreadLocalとThreadLocal.get()を使用します)。これは、将来のバグ(他の​​開発者が不注意に設計上の仮定に違反する可能性がある場合)と、使用しているExecutorメソッドの内部実装に関する仮定を行うことの両方を防ぐためです。順次であり、execute(runnable)の各呼び出しで必ずしも同じスレッドである必要はありません。

于 2009-12-14T16:31:08.947 に答える
0

スレッドは1つだけであり、そのスレッドだけがフィールドにアクセスするため、この特定のコードで状態が不揮発性であることはまったく問題ありません。使用している唯一のスレッド内でこのフィールドの値のキャッシュを無効にすると、パフォーマンスが低下します。

ただし、ループを実行しているメインスレッドでstateの値を使用する場合は、フィールドを揮発性にする必要があります。

    for (int i=0; i<10; ++i) {
            ex.execute(test);
            System.out.println(test.getState());
    }

ただし、スレッド間に同期がないため、これでもvolatileでは正しく機能しない可能性があります。

フィールドはプライベートであるため、メインスレッドがこのフィールドにアクセスできるメソッドを実行する場合にのみ問題が発生します。

于 2009-12-14T16:37:23.080 に答える
-1

ExecutorServiceがシングルスレッドの場合、共有状態はないため、その周りに問題が発生する可能性があるかどうかはわかりません。

Testただし、クラスの新しいインスタンスを各呼び出しに渡す方が理にかなっていますexecute()か?すなわち

for (int i=0; i<10; ++i)
    ex.execute(new Test());

このように、共有状態はありません。

于 2009-12-14T16:13:41.213 に答える