2009-06-13 15 views
11

おそらく同様の質問:Javaの変数がデフォルトで揮発性でないのはなぜですか?

Do you ever use the volatile keyword in Java?


今日、私は私のゲームをデバッグました。数分ごとに表示される非常に困難なスレッド問題がありましたが、再現するのは難しかったです。最初に私の方法のそれぞれに​​キーワードを追加しました。それはうまくいかなかった。その後、すべてのフィールドに volatileというキーワードを追加しました。問題はちょうどそれ自身を修正するようだった。

いくつかの実験の後、担当するフィールドがゲームの現在の状態(再生中またはビジー状態のいずれか)を記録したGameStateオブジェクトであることがわかりました。ビジー状態のとき、ゲームはユーザーの入力を無視します。私が持っていたのは、常にstateという変数を変更したスレッドでしたが、イベントスレッドはstateという変数を読み取りました。しかし、あるスレッドが変数を変更した後、他のスレッドが変更を認識するまで数秒かかるため、最終的に問題が発生します。

状態変数volatileを作成することで修正されました。

Javaの変数がデフォルトでvolatileでないのはなぜですか?また、volatileキーワードを使用しない理由は何ですか?

+11

同じ理由で、メソッドはデフォルトで同期されません。 –

+6

@mP:そうは思わない。揮発性と同期化は同等ではない:ボラティリティはデッドロックを引き起こすことができない。 –

答えて

32

長い話を簡単にするために、JavaやC#のような揮発性変数は決してスレッド内でローカルにキャッシュされません。これは、異なるキャッシュで実行されているスレッドを持つマルチプロセッサ/マルチコアCPUを扱っていない限り、同じキャッシュを探している場合を除き、意味がありません。変数をvolatileとして宣言すると、すべての読み書きが実際のメインメモリ位置からまっすぐに進みます。キャッシュは含まれていません。これは最適化に際して意味があり、不必要に(ほとんどの変数が揮発性である必要がない場合)、比較的小さな利益のためにパフォーマンス上のペナルティがかかります。

+6

単一のコアプロセッサであっても、コンパイラ(JITコンパイラを含む)が変数へのアクセスを最適化することを決定することがあります。 –

+0

@Chris:.NETのバックグラウンドから来たので、Javaは異なるかもしれませんが、インスタンス変数が使用されていないことが検出され、ローカル変数がvolatile宣言できない限り、インスタンス変数を最適化できるとは思いません(本当にそれは必要ないでしょう)、そうですか? –

+8

@Adam:それを完全に最適化することはできません。しかし、 "y = x * x"のようなものは、xの値を2回ではなく1回だけ得るためにメモリに行くことができます。それは簡単な例です...今、xがいくつかのループを通して広がり、何度かチェックされたと想像してください。コンパイラがxの値を変更することがないと判断した場合は、一度読み取ってローカルに保持することになります。 –

5

コンパイラは揮発性変数を最適化できないためです。

volatileは、変数がいつでも変更できることをコンパイラに伝えます。したがって、変数がそれに応じて変更および最適化されないと想定することはできません。

2

変数をvolatile宣言すると、一般にパフォーマンスに大きな影響があります。従来のシングルスレッドシステムでは、何が揮発性である必要があるかを比較的簡単に知ることができました。それはハードウェアにアクセスするものでした。

マルチスレッドの場合はもう少し複雑になる可能性がありますが、一般的には、通知とイベントキューを使用して、マジック変数を使ってデータを渡すことをお勧めします。 Javaではそれほど重要ではないかもしれません。 C/C++では、基礎となるハードウェアによってこれらの変数をアトミックに設定できない場合、問題が発生します。

+0

奇妙なことに、揮発性変数の私の唯一の経験は、特にマルチスレッドアクセスのためです。シングルスレッド環境で揮発性変数の背後にある理由は何でしょうか? –

+1

@Adam:JavaとC#では、私はJNIがあなたにいくつかのゲームをさせるかもしれないとは思っていますが、それほど確かではありません。 C/C++環境では、このメモリが実行中のプログラム以外の場所、すなわちそのメモリ位置にマップされたハードウェアから変更される可能性があることを示すためには、揮発性が重要でした。 JavaとC#は一般的にハードウェアの近くで実行されないので、別のゲームかもしれません。 –

+0

@AdamRobinson:単一のスレッド環境でも、Javaは独立したコマンドを並べ替える権利を留保します。したがって、例えば: 'x = 1; y = 2; 'はy = 2に変更できます。 x = 1; '。しかし 'x = z ++; y = z ++; 'はy = z ++に変更されるべきではありません。 x = z ++; ' JVMは、外部からの可視性を持つコマンドの順序を変更することは想定されていません。たとえば、 'x = 1; System.out.println( "x is set"); y = 2; 'は再注文できません。 Javasの外部可視性ロジックを信頼しない場合(実際のメモリを監視している場合など)、volatileを使用して変数を最適化すべきではないものとしてマークすることができます。 –

6

他の人がvolatileにデフォルトするのが悪い理由を指摘するのは間違いありませんが、別の点もあります。コードにバグがありそうです。 変数は揮発性にする必要はほとんどありません。同期されたキーワード、またはjava.util.concurrencyのAtomicXxxオブジェクトを使用して、変数へのアクセスを適切に同期する方法は常にあります。例外には、これらを操作するJNIコードが含まれます(同期ディレクティブ)。

volatileを追加する代わりに、問題が解決された理由を把握したい場合があります。それを解決するのは唯一の方法ではなく、おそらくもっと良い方法があります。

+1

これは当てはまるかもしれませんが、ロックフリーのコードを書こうとしている可能性もあります。すべてが読んでいる場合は、排他ロックを取得したり、セマフォを使用する必要は必ずしもありません。ちょうど私の.02 –

+0

真実、それは可能であり、揮発性物質は片道です。しかし、揮発性のオーバーヘッド(他の理由が指摘する理由による)は、明示的な同期のオーバーヘッドを超える可能性があり、低いとは思われません。特に最適化されたj.u.concurrentがどのように最適化されているかを考えると、 – StaxMan

10

実際には、低レベルのスレッドセーフ、ロックフリーのコードを作成しようとしているときにのみ揮発性物質が必要です。ほとんどのコードはであるべきではありません。スレッドセーフまたはロックフリーです。私の経験上、ロックフリープログラミングは、ロックのために重要なパフォーマンスヒットが発生するを行うより単純なバージョンであることが判明した後で試してみる価値があります。

java.util.concurrentには他のビルディングブロックを使用することをお勧めしますが、その中にはロックフリーのものもありますが、低レベルですべて自分自身でやっているように頭を悩ますものではありません。

ボラティリティには独自のパフォーマンスコストがあり、のほとんどのコードではコードにこれらのコストが発生する必要はありません。

8

個人的に私は、フィールドはデフォルトでは最後になり、余分なキーワードでしか変更できないはずだと思っていますが、そのボートは時間の経過とともに航海しています。 ;)

関連する問題