13

Javaメモリモデルによる「効果的に不変のオブジェクト」の動作を正しく理解していることを確認したいと思います。

事実上不変として公開したい可変クラスがあるとしましょう。

class Outworld {
  // This MAY be accessed by multiple threads
  public static volatile MutableLong published;
}

// This class is mutable
class MutableLong {
  private long value;

  public MutableLong(long value) {
    this.value = value;
  }

  public void increment() {
    value++;
  }

  public long get() {
    return value;
  }
}

次のことを行います。

// Create a mutable object and modify it
MutableLong val = new MutableLong(1);
val.increment();
val.increment();
// No more modifications
// UPDATED: Let's say for this example we are completely sure
//          that no one will ever call increment() since now

// Publish it safely and consider Effectively Immutable
Outworld.published = val;

問題は次のとおりです。Javaメモリモデルは、すべてのスレッドが持っている必要があることを保証しますOutworld.published.get() == 3か?

Java Concurrency In Practiceによると、これは正しいはずですが、間違っている場合は訂正してください。

3.5.3。安全な出版イディオム

オブジェクトを安全に公開するには、オブジェクトへの参照とオブジェクトの状態の両方を同時に他のスレッドに表示する必要があります。適切に構築されたオブジェクトは、次の方法で安全に公開できます。-
静的初期化子からオブジェクト参照を初期化する。
-それへの参照を揮発性フィールドまたはAtomicReferenceに保存します。
-それへの参照を適切に構築されたオブジェクトの最終フィールドに格納します。または
-ロックによって適切に保護されているフィールドに参照を保存します。

3.5.4。効果的に不変のオブジェクト

安全に公開された効果的に不変のオブジェクトは、追加の同期なしで任意のスレッドで安全に使用できます。

4

3 に答える 3

9

はい。の書き込み操作の後には、読み取りの前に(揮発性の)関係MutableLongが続きます。happens-before

(スレッドOutworld.publishedがそれを読み取って別のスレッドに安全に渡さない可能性があります。理論的には、それは以前の状態を見る可能性があります。実際には、それが起こっているとは思いません。)

于 2012-04-20T22:51:05.897 に答える
5

Javaメモリモデルが次のことを保証するために満たす必要のある条件がいくつかありますOutworld.published.get() == 3

  • を作成してインクリメントし、フィールドMutableLongを設定する投稿したコードのスニペットは、ステップ間の可視性で発生する必要があります。これを簡単に実現する1つの方法は、すべてのコードを単一のスレッドで実行することです。これにより、「as-if-serialセマンティクス」が保証されます。それがあなたの意図したことだと思いますが、指摘する価値があると思いました。Outworld.published
  • の読み取りは、割り当てからのセマンティクスの後に発生するOutworld.published必要があります。この例としては、同じスレッドを実行してから、値を読み取ることができる他のスレッドを起動する場合がありますこれにより、「シリアルのように」セマンティクスが保証され、割り当て前の読み取りの並べ替えが防止されます。Outworld.published = val;

これらの保証を提供できる場合、JMMはすべてのスレッドを保証します。を参照してくださいOutworld.published.get() == 3


ただし、この分野の一般的なプログラム設計のアドバイスに興味がある場合は、このまま読み進めてください。

他のスレッドがの異なる値を決してOutworld.published.get()見ないことを保証するために、あなた(開発者)はあなたのプログラムがいかなる方法でも値を変更しないことを保証しなければなりません。続いて実行するOutworld.published = differentVal;か、またはOutworld.published.increment();。それを保証することは可能ですが、可変オブジェクトと、複数のスレッドのグローバルアクセスポイントとして静的な非最終フィールドの両方を回避するようにコードを設計すると、はるかに簡単になります。

  • 公開する代わりにMutableLong、関連する値を別のクラスの新しいインスタンスにコピーします。このインスタンスの状態は変更できません。ImmutableLong例:構築時にフィールドに割り当てvalueられ、メソッドfinalを持たないクラスを導入します。increment()
  • 複数のスレッドが静的な非最終フィールドにアクセスする代わりに、オブジェクトをパラメーターとしてCallable/Runnable実装に渡します。これにより、1つの不正なスレッドが値を再割り当てして他のスレッドに干渉する可能性がなくなり、静的フィールドの再割り当てよりも推論が容易になります。(確かに、レガシーコードを扱っている場合、これは口で言うほど簡単ではありません)。
于 2012-04-21T11:09:49.170 に答える
3

問題は次のとおりです。Javaメモリモデルは、すべてのスレッドがOutworld.published.get()== 3でなければならないことを保証しますか?

簡単な答えはnoです。他のスレッドがOutworld.published読み取られる前にアクセスする可能性があるためです。

実行された瞬間の後、-はい-でOutworld.published = val;他の変更が行われないという条件の下で、それは常にです。val3

ただし、いずれかのスレッドが実行val.incrementされる場合、その値は他のスレッドとは異なる可能性があります。

于 2012-04-21T03:39:20.610 に答える