静的変数はスレッド間で共有されますか?


95

スレッドの上級Javaクラスの私の先生は、私にはわからないことを言っていました。

彼は、以下のコードは必ずしもready変数を更新するとは限らないと述べた。彼によると、特に各スレッド(メインスレッドとReaderThread)が独自のプロセッサで実行されているため、2つのスレッドが必ずしも静的変数を共有していないため、同じレジスタ/キャッシュ/ etcと1つのCPUを共有していない他を更新しません。

基本的readyに、メインスレッドで更新される可能性はありますが、では更新されない可能性があるReaderThreadため、ReaderThread無限にループします。

彼はまた、プログラムが0またはを印刷することは可能であると主張した42。どの42ように印刷できるか理解していますが、できません0。彼numberは、変数がデフォルト値に設定されている場合にこれが当てはまるだろうと述べました。

静的変数がスレッド間で更新されることが保証されていないのではないかと思ったのですが、これはJavaにとって非常に奇妙な印象を受けます。ready揮発性にすることでこの問題は修正されますか?

彼はこのコードを示しました:

public class NoVisibility {  
    private static boolean ready;  
    private static int number;  
    private static class ReaderThread extends Thread {   
        public void run() {  
            while (!ready)   Thread.yield();  
            System.out.println(number);  
        }  
    }  
    public static void main(String[] args) {  
        new ReaderThread().start();  
        number = 42;  
        ready = true;  
    }  
}

非ローカル変数の可視性は、それらが静的変数であるか、オブジェクトフィールドであるか、配列要素であるかに依存しません。それらはすべて同じ考慮事項を持っています。(配列要素を揮発性にすることができないという問題があります。)
PaŭloEbermann、2011

1
「0」が表示される可能性があると彼が考えるアーキテクチャの種類を教師に尋ねます。しかし、理論的には彼は正しい。
bestss

4
@bestsssそのような質問をすることは、彼が言っていることの全体のポイントを逃したことを教師に明らかにするでしょう。要は、有能なプログラマーは何が保証されているか、何が保証されていないかを理解し、保証されていないものに依存しない、少なくとも保証されていないものとその理由を正確に理解していないということです。
デビッドシュワルツ

それらは、同じクラスローダーによってロードされたすべての間で共有されます。スレッドを含みます。
ローンの侯爵

あなたの先生(そして受け入れられた答え)は100%正しいですが、めったに起こらないことを述べます-これは、何年もの間隠されて、それが最も有害であるときにのみ現れる問題です。問題を明らかにしようとする短いテストでも、すべてが問題ないように振る舞う傾向があります(おそらく、JVMが多くの最適化を行うための時間がないため)、これは、知っておくべき本当に良い問題です。
ビルK

回答:


75

可視性に関しては、静的変数について特別なことは何もありません。それらがアクセス可能である場合、どのスレッドでもアクセスできます。そのため、それらが公開されているため、同時実行の問題が発生する可能性が高くなります。

JVMのメモリモデルによって課される可視性の問題があります。ここでは、メモリモデルと、書き込みがスレッドから見えるようになる方法について説明する記事を示します発生前の関係を確立しない限り、1つのスレッドがタイムリーに他のスレッドから見えるようになる変更(実際には、JVMはこれらの変更をユーザーに見えるようにする義務を一切負わない)に頼ることはできません。。

これがそのリンクからの引用です(Jed Wesley-Smithのコメントで提供)。

Java言語仕様の第17章では、共有変数の読み取りや書き込みなどのメモリ操作での前に発生する関係を定義しています。1つのスレッドによる書き込みの結果は、読み取り操作の前に書き込み操作が発生した場合にのみ、別のスレッドによる読み取りから見えることが保証されています。同期された揮発性の構成体、およびThread.start()メソッドとThread.join()メソッドは、発生前の関係を形成できます。特に:

  • スレッド内の各アクションは、プログラムの順序で後から来るそのスレッド内のすべてのアクションの前に発生します。

  • モニターのアンロック(同期ブロックまたはメソッド出口)は、同じモニターの後続のすべてのロック(同期ブロックまたはメソッドエントリー)の前に発生します。また、発生前の関係は推移的であるため、ロックを解除する前のスレッドのすべてのアクションは、そのモニターをロックしているスレッドに続くすべてのアクションの前に発生します。

  • 揮発性フィールドへの書き込みは、同じフィールドの後続のすべての読み取りの前に行われます。揮発性フィールドの書き込みと読み取りは、モニターの開始および終了と同様のメモリー一貫性の効果がありますが、相互排他ロックを伴いません。

  • スレッドで開始する呼び出しは、開始されたスレッドのアクションの前に発生します。

  • スレッド内のすべてのアクションは、他のスレッドがそのスレッドの結合から正常に戻る前に発生します。


3
実際には、「タイムリーに」と「これまで」は同義語です。上記のコードが終了することはほとんどありません。
ツリー

4
また、これは別のアンチパターンを示しています。volatileを使用して複数の共有状態を保護しないでください。ここで、numberとreadyは2つの状態であり、両方を一貫して更新/読み取るには、実際の同期が必要です。
ツリー

5
最終的に見えるようになる部分は間違っています。明示的な問題が発生し、前にすることなく、関係の任意の書き込みがするという保証はありません今まで JITは、その権利をレジスタに読み込みを押し付けるために、次にあなたが任意の更新を参照してくださいことは決してないだろう、かなりの範囲内であるとして、別のスレッドで見られることは。最終的な負荷は幸運であり、信頼してはなりません。
Jed Wesley-Smith、

2
「volatileキーワードを使用するか同期する場合を除きます。」「ライターとリーダーの間に関連する発生前の関係がない限り」とこのリンクを読む必要があります
。download.oracle.com/ javase / 6

2
@bestsssはよく見つかりました。残念ながら、ThreadGroupは多くの点で壊れています。
Jed Wesley-Smith、

37

彼は視界について話していて、文字通りに受け取られるべきではありませんでした。

静的変数は確かにスレッド間で共有されますが、あるスレッドで行われた変更が別のスレッドにすぐに表示されない場合があるため、変数のコピーが2つあるように見えます。

この記事は、彼が情報を提示した方法と一致する見解を示しています。

まず、Javaメモリーモデルについて少し理解する必要があります。私はそれを簡単かつうまく説明するために長年にわたって少し苦労しました。今日のところ、私がそれを説明するために考えることができる最良の方法は、あなたがそれをこのように想像する場合です:

  • Javaの各スレッドは個別のメモリ空間で発生します(これは明らかに正しくないため、この問題については我慢してください)。

  • メッセージパッシングシステムの場合と同様に、これらのスレッド間で通信が行われることを保証するには、特別なメカニズムを使用する必要があります。

  • 1つのスレッドで発生するメモリの書き込みは、「リーク」して別のスレッドに認識される可能性がありますが、これは保証されていません。明示的な通信がないと、どの書き込みが他のスレッドに表示されるか、またはそれらが表示される順序でさえ保証できません。

...

スレッドモデル

ただし、これも、スレッド化と揮発性について考えるための単なるメンタルモデルであり、文字通りJVMの動作方法ではありません。


12

基本的にはそうですが、実際には問題はより複雑です。共有データの可視性は、CPUキャッシュだけでなく、命令の順不同の実行によっても影響を受ける可能性があります。

したがって、Javaはメモリモデルを定義します。これは、スレッドが共有データの一貫した状態を確認できる状況を示します。

特定のケースでは、追加volatileすることで可視性が保証されます。


8

もちろん、両者は同じ変数を参照するという意味で「共有」されますが、必ずしも互いの更新を参照する必要はありません。これは、静的な変数だけでなく、どの変数にも当てはまります。

理論的には、変数が宣言されていvolatileないか、書き込みが明示的に同期されていない限り、別のスレッドによる書き込みは異なる順序で表示される可能性があります。


4

単一のクラスローダー内では、静的フィールドは常に共有されます。データをスレッドに明示的にスコープするには、などの機能を使用しますThreadLocal


2

静的プリミティブ型変数を初期化すると、Javaのデフォルトで静的変数の値が割り当てられます

public static int i ;

このような変数を定義すると、iのデフォルト値は0になります。そのため、0になる可能性があります。メインスレッドは、ブール値をtrueに更新します。レディは静的変数なので、メインスレッドと他のスレッドが同じメモリアドレスを参照しているため、レディ変数が変化します。したがって、2次スレッドはwhileループから抜け出し、値を出力します。印刷するとき、numberの初期化された値は0です。スレッドプロセスが、メインスレッドの更新番号変数の前にwhileループを通過した場合。その後、0を印刷する可能性があります


-2

@dontocsataあなたは先生に戻って彼に少し学校を教えることができます:)

現実世界からのメモはほとんどなく、見たり聞いたりする内容に関係なく。注意してください、以下の単語は、この特定のケースに関するもので、表示されている順序と同じです。

次の2つの変数は、事実上すべての既知のアーキテクチャーの下で同じキャッシュラインに常駐します。

private static boolean ready;  
private static int number;  

Thread.exit(メインスレッド)は終了exitすることが保証され、スレッドグループのスレッドの削除(および他の多くの問題)により、メモリフェンスが発生することが保証されています。(これは同期呼び出しであり、デーモンスレッドが残っていない場合はThreadGroupも終了する必要があるため、同期部分なしで実装する単一の方法はありません)。

開始されたスレッドReaderThreadはデーモンではないため、プロセスを存続させます!したがってreadynumber一緒にフラッシュされます(またはコンテキストスイッチが発生した場合はその前の数)。この場合、並べ替える本当の理由はありません。少なくとも1つは考えられません。何かを見るには、本当に奇妙なものが必要です42。繰り返しますが、両方の静的変数が同じキャッシュラインにあると思います。4バイト長のキャッシュライン、または連続した領域(キャッシュライン)にそれらを割り当てないJVMを想像することはできません。


3
@bestsssは今日ではすべて当てはまりますが、プログラムのセマンティクスではなく、現在のJVM実装とハードウェアアーキテクチャに依存しています。これは、動作する可能性があるにもかかわらず、プログラムがまだ壊れていることを意味します。指定された方法で実際に失敗するこの例の簡単な変形を簡単に見つけることができます。
Jed Wesley-Smith

1
私はそれが仕様に準拠していないと言いましたが、教師として少なくともいくつかの商品アーキテクチャで実際に失敗する可能性のある適切な例を見つけたので、例は一種の現実です。
bestss

6
おそらく、私が見たスレッドセーフなコードの記述に関する最悪のアドバイス。
Lawrence Dol、2011

4
@Bestsss:あなたの質問に対する簡単な答えは、単に「特定のシステムや実装の副作用ではなく、仕様とドキュメントに対するコード」です。これは、基盤となるハードウェアに依存しないように設計された仮想マシンプラットフォームでは特に重要です。
Lawrence Dol、2011

1
@Bestsss:教師のポイントは、(a)コードをテストするとうまく機能する可能性があり、(b)コードが仕様の保証ではなくハードウェアの動作に依存しているため、コードが破損していることです。問題は、問題はないように見えますが、問題があるということです。
Lawrence Dol、2011
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.