7

このクラスを想定すると:

public class AmIThreadSafe {

    private int a;
    private int b;

    AmIThreadSafe(int a, int b) {
        this.a = a;
        this.b = b;
    }
}

このクラスへのインスタンスの参照(として宣言されている)が、 (参照)がエスケープvolatileされるとすぐに、いくつかのスレッド(競合状態につながる)によってアクセス可能であると仮定します。this

volatile AmIThreadSafe instance = new AmIThreadSafe(1,2);

ここでは、スレッドで読み取る前にinstance、参照を割り当てるという事実が発生すると確信しています。

しかし、AmIThreadSafe'sフィールドはどうですか?

外部キーワードは、フィールドに関する関係volatileも意味しますか?または、コンストラクター中にステートメントが並べ替えられる可能性があるために、スレッドに古い値(この場合はデフォルト値以降)が表示される可能性はありますか?happens-beforeab0int

言い換えれば、JMMで予期しないことを宣言ab finalたり、防止したりする必要がありますか、それともインスタンスの参照に十分に示しているだけですか?volatilevolatile

----------------更新された投稿-良い答え: --------------------------- -

次の記事は、そのサンプルによって、私の場合、永続的な発生前の関係を防ぐJMM最適化から保護されaていることを確認しています。b

http://jeremymanson.blogspot.fr/2008/11/what-volatile-means-in-java.html

4

6 に答える 6

4

いいえ、それを揮発性にするのに十分ではありません。ただし、スレッドセーフは使用法によって異なります。たとえば、別のスレッドが値を変更している場合、これでも予期しない結果が生じる可能性があります。

public簡単にするために変数を仮定する

volatile AmIThreadSafe instance = new AmIThreadSafe(1,2);
if (instance.x == 0) {
   // instance.x might have changed between checking and assigning
   instance.x = instance.x + 1;
}

volatile変数にのみ適用されます(たとえば、xであるという理由だけでy自動的に適用されるわけではありません)。これはから明らかなはずですvolatileinstanceJLS 8.3.1.4

于 2012-11-14T00:11:21.343 に答える
4

として宣言してもそのフィールドは作成instanceされvolatileませんvolatileが、私があなたの質問を正しく理解していれば、そうです—はい、あなたの場合はそれで十分です。

仕様の§17.4.5による:

  • あるvolatileスレッドでの書き込みが発生します-後続volatileの別のスレッドでの読み取りの前に。
  • 同じスレッド内のステートメントには、予想どおりの発生前の関係があります。
  • 起こる-関係が推移的になる前に。

したがって、スレッドinstanceが初期化されたと認識する場合、そのinstance 前にの初期化が発生instanceし、その前に'sフィールドの初期化が発生したため、スレッドはinstance'sフィールドが初期化されたと認識します。

于 2012-11-14T00:27:16.560 に答える
2

あなたのvolatile場合、はの参照にのみ適用されますAmlThreadSafe。それでも、インスタンス変数(aおよびb)を作成するか、ブロックvolatile内でそれらにアクセスする必要があります。synchronizedそうしないと、古いデータを取得する可能性があります。

于 2012-11-14T00:14:01.323 に答える
2

はい。

thread 1                 thread 2

1 write(a)

2 write(instance)

                         3 read(instance)

                         4 read(a)

インスタンスは揮発性であるため、[2]は[3]の前に発生します。

発生する前は推移的であるため、hb(1,2)、hb(2,3)、hb(3,4)、したがってhb(1,4)があります。

于 2012-11-14T00:56:22.260 に答える
0

aとがコンストラクターでのみ変更される場合b、この場合、オブジェクトは参照が割り当てられる前に作成(aおよび設定)され、他のスレッドにはローカルにキャッシュされたメモリのコピーがないため、問題ありません。これらの場所は、スレッドがこれまで見ることができなかった新しいオブジェクトであるためです。言い換えると、オブジェクトの参照がに割り当てられる前にコンストラクターが完全に実行されるため、別のスレッドが「デフォルト」値の0を認識できる可能性はないと思います。binstanceabinstance

ただし、コンストラクターの後でとを変更できる場合ab、ここでの他の答えは正しいです。それらを同期する必要があります。

それを想定しabコンストラクターの外部で変更されない場合は、安全のために、とにかく最終的にしない理由はありません。

于 2012-11-14T00:16:36.123 に答える
0

例 :

class Something{
  private volatile static Something instance = null;
  private int x;
  private int y;
  private Something(){
    this.x = 1;
    this.y = 2;
  }
  public static Something getInstance() {
    if (instance == null) {
        synchronized (Something.class) {
            if (instance == null)
                instance = new Something();
            }
         }  
     }
   return instance; 
  }

}

説明 :


上記のコードがあるとしましょう:

ここで、インスタンスがしばらくの間揮発性ではないと仮定しましょう。

スレッド#1
getInstanceメソッドを呼び出し、インスタンス値をチェックします{null以降}、IF条件内に入ります。ロックにアクセスすると、インスタンス== nullが再び検出され、Somethingコンストラクターが呼び出されます。コンストラクター本体の内部に入ります。

スレッド#1がコンストラクター本体の内部に入るとすぐに、コンテキストスイッチが発生し、スレッド#2が実行されるようになります。

スレッド#2
呼び出しはインスタンスを取得しますが、突然そのインスタンスがnullではないことに気付きますか?なぜ{理由はこの直後に議論する}ので、部分的に構築されたオブジェクトを参照に割り当てて返します。

現在の状況は次のようになります。Thread#1はまだオブジェクトを完全に構築する必要があります{完全に構築する必要があります}そしてThread#2は部分的に構築されたオブジェクトの参照を取得し、それをsayreference.xのように使用すると//"xを出力します「1」ではなく「デフォルト値」

スレッド#2の場合、部分的に構築されたオブジェクト参照が返されるのはなぜですか?理由は単純です:ステートメントの並べ替え。手順は、オブジェクトの作成と参照の関連付けが簡単です。

  1. ヒープにメモリを割り当てます。
  2. クラスメンバーを初期化するコンストラクターの本体を実行します。
  3. 上記の手順が完了したら、新しく作成されたオブジェクトへの参照。

ただし、コンパイラがこれらの命令を順不同で実行する場合があります。つまり、次の
ようになります。

  1. ヒープにメモリを割り当てます。
  2. 新しく作成されたオブジェクトへの参照。
  3. クラスメンバーを初期化するコンストラクターの本体を実行します。

上記の2つの手順が実行され、コンテキスト切り替えが発生した場合、参照は初期化されていないオブジェクトを指すか、Inside Constructor Bodyコンテキスト切り替えが発生した場合、参照は部分的に初期化されたオブジェクトを参照します。

このようなシナリオが発生した場合、参照はnullでも完全でもないため、シングルトンの動機が失われます。

さて、どのように揮発性がそのような困惑から私たちの命を救うか:
私たちが知っているように、揮発性は2つの原則で働きます:1)可視性2)関係の前に起こります。関係がここに現れる前に今起こります。

したがって、参照は揮発性の書き込みであるため、すべてのステートメントは揮発性の書き込みの前に実行する必要があります。オブジェクトの構築手順を見ると、次のようになります。

  1. オブジェクトにメモリを割り当てます
  2. メンバー変数を初期化します{コンストラクター本体}
  3. オブジェクト参照を揮発性変数インスタンスに割り当てます。

ステップ3には揮発性変数の書き込みがあり、以前のように..すべてのステートメントの書き込みはステップ3で使用できることが保証されています。また、揮発性であるため、揮発性ステートメントと不揮発性ステートメントの間で並べ替えは発生しません。これは、古いJavaでは当てはまりませんでした。メモリモデル。
したがって、ステップ3を実行する前に、ステップ1とステップ2が実行され、ステップ3で使用できることが保証されます。{ステップ1とステップ2が実行される順序については、気にしません。}

したがって、このスレッドから、完全に作成されたオブジェクトまたはnullが表示されます

于 2015-01-11T15:54:58.223 に答える