11

いくつかのOSSユニットテストでこのコードを頻繁に参照していますが、スレッドセーフですか? whileループはinvocの正しい値を見ることが保証されていますか?同期化されていないため、javaの整数スレッドセーフが読み込まれますか?

いいえ。誰が誰がどのCPUアーキテクチャが失敗するかを知っている人を指摘する。

private int invoc = 0; 

    private synchronized void increment() { 
    invoc++; 
    } 

    public void isItThreadSafe() throws InterruptedException { 
     for (int i = 0; i < TOTAL_THREADS; i++) { 
     new Thread(new Runnable() { 
      public void run() { 
      // do some stuff 
      increment(); 
      } 
     }).start(); 
     } 
     while (invoc != TOTAL_THREADS) { 
     Thread.sleep(250); 
     } 
    } 
+0

100%とは言えませんが、非揮発性フィールドの非同期読み出しは、(書き込み*が*によってフラッシュされていても)最新の値を読み込むことが保証されていないようです。 'increment()'メソッドの 'synchronized'キーワードを参照してください)。Javaメモリモデルは、確かに言えば十分に分かりません。 – dlev

+0

@dlev質問です。) – krosenvold

+0

「私はいつも働いてくれました!」と答えようとしていました。しかし、intsには "Yes"、longsとdoubleには "Maybe"と言うhttp://stackoverflow.com/questions/1006655/are-java-primitive-ints-atomic-by-design-or-by-accidentも見つかりました。 –

答えて

17

いいえ、スレッドセーフではありません。 invocをvolatile宣言するか、同じロックで同期してアクセスするか、AtomicIntegerを使用するように変更する必要があります。同期メソッドを使用してinvocをインクリメントしますが、同期をとって読み取るのでは十分ではありません。

JVMは、CPU固有のキャッシュや命令の並べ替えなど、多くの最適化を行います。これは、volatileキーワードとロックを使用して、いつ自由に最適化できるかを決定し、他のスレッドが読むために利用可能な最新の値を持っている必要があるかどうかを判断します。したがって、読者がロックを使用していない場合、JVMはそれを失効させないことを知ることができません。実践でのJava並行処理(セクション3.1.3)を書き込み、同期する必要読み取っ両方の方法について説明しますから

この引用:

固有ロックは、あるスレッドが効果を見ていることを保証するために使用することができます図3.1に示すように、別のものを予測可能な形で提供します。スレッドAが同期ブロックを実行し、その後スレッドBが同じロックで保護された同期ブロックに入ると、ロックを解放する前にAに見える変数の値は、ロックを獲得するとBに見えることが保証されます。言い換えれば、Aが同期ブロック内または同期ブロック前に行ったことは、同じロックによって保護された同期ブロックを実行するときに、Bが見ることができます。同期がなければ、そのような保証はありません。

次のセクション(3.1.4)は、揮発性の使用カバー:

Java言語も変数の更新が予想伝播されることを保証するために、同期化の代替として、より弱い形態、揮発性変数を、提供他のスレッドに。フィールドがvolatile宣言されると、コンパイラとランタイムは、この変数が共有され、その上での操作を他のメモリ操作と並び替えるべきではないことに注意してください。揮発性変数は、他のプロセッサから隠されているレジスタやキャッシュにはキャッシュされないため、揮発性変数を読み取ると、常に最新の書き込みがスレッドによって返されます。

私たちはデスクトップ上にシングルCPUマシンを持っていましたが、通常は本番環境のマルチプロセッサボックスで実行されるまで、コードを書いても問題はありません。 Visiblityの問題を引き起こす要因、CPUローカルキャッシュや命令の並べ替えなどの要因の中には、マルチプロセッサマシンから期待されるものがあります。しかし、明らかに不必要な命令がすべてのマシンで発生する可能性があります。 JVMが、変数の最新の値を読者に見せられるようにすることは何もありません。あなたはJVM実装者の手に委ねられています。だから、このコードはどんなCPUアーキテクチャにとってもうまくいかないだろうと私には思われます。

2

private volatile int invoc = 0; 

トリックを行います。

Are java primitive ints atomic by design or by accident?を参照してください。関連するJava定義のいくつかのサイト。どうやらintは問題ありませんが、double doubleは&ではないかもしれません。


編集、アドオン。質問は、 "正しい値のinvocを見ますか?"と尋ねます。 「正しい価値」とは何ですか?時間空間の連続体と同様に、スレッド間には同時性は実際には存在しません。上記の投稿の1つは、値が最終的にフラッシュされ、もう1つのスレッドがそれを取得することに注意してください。コードは「スレッドセーフ」ですか?この場合、シーケンシングのばらつきに基づいて「誤動作」しないので、「はい」と言います。

+1

上記の質問の引用が実際に別のスレッドへの可視性をカバーしているかわかりません – krosenvold

+1

形而上学を取らずに、私は理論的なチャンスがあると思います外側のループは0以外の何も見ることはありません。 – krosenvold

0

私がコードを理解する限り、安全でなければなりません。バイトコードは並べ替えることができます。しかし、最終的にinvocはメインスレッドと再び同期する必要があります。 invocが正しくインクリメントされるように、いくつかのレジスタにinvocが一貫して表示されるように、同期を保証します。ある時点でこの値はフラッシュされ、小さなテストは成功します。

私はに投票した回答には、それが匂いがあるので、このようなコードを修正するのは間違いないと思います。しかし、それについて考えると、私はそれを安全だと考えます。

+0

これは可視性に関するものであり、並べ替えの問題はありません。 VM **は、メモリの読み取りを完全に最適化し、_invoc_をアクセスが同期/揮発性でないスレッドの定数のように扱います。これは時には頻繁に動作するかもしれませんが、これは修正する必要があります。 – Boris

+0

私は私の腸の感情をもっと信じるべきだと知っています:-) –

1

理論的には、読み取りがキャッシュされている可能性があります。 Javaメモリモデルではそれを防ぐことはできません。

実際には、(あなたの特定の例では)それは起こりそうにありません。問題は、JVMがメソッド呼び出し全体で最適化できるかどうかです。

read #1 
method(); 
read #2 

(CPUレジスタに格納することができます)は、リード#1の結果を再利用することができます#2を読ん理由にJVMの場合、それはmethod()が全く同期アクションが含まれていないことを確実に知る必要があります。 method()がインライン化されていない限り、JVMはフラットコードから、read#1とread#2の間にsync/volatileまたはその他の同期アクションがないことを見ることができない限り、これは一般的に不可能です。それは安全に#2を読むことを排除することができます。

あなたの例では、方法はThread.sleep()です。それを実装する1つの方法は、CPUの周波数に応じて、特定の時間にビジーループにすることです。その後、JVMはインライン展開し、read#2を取り除きます。

もちろん、sleep()のこのような実装は非現実的です。これは通常、OSカーネルを呼び出すネイティブメソッドとして実装されます。問題は、JVMがそのようなネイティブメソッド全体で最適化できるかどうかです。

JVMがいくつかのネイティブメソッドの内部動作の知識を持っているため、それらをまたがって最適化することはできますが、sleep()はそのように扱われることはありません。 sleep(1ms)には何百万ものCPUサイクルが必要となります。

-

この議論は、データ競合の最大の問題を明らかに - それはそれについて推論するためにあまりにも多くの手間がかかります。プログラムが「正しく同期されていない」場合、プログラムが必ずしも間違っているとは限りませんが、間違っていないことを証明するのは簡単な作業ではありません。プログラムが正しく同期され、データ競争がない場合、人生ははるかに簡単です。

+0

奇妙なことは、私がこの正確な事件が起こっているのを理論的に見たかどうかはほとんど確かです。 – krosenvold

0

「int」を使用する必要がない場合は、AtomicIntegerをスレッドセーフな代替方法としてお勧めします。

関連する問題