16

これが取引です。私は「プログラムコード」と呼ぶデータを含むハッシュマップを持っています。それは次のようにオブジェクトに存在します。

Class Metadata
{
    private HashMap validProgramCodes;
    public HashMap getValidProgramCodes() { return validProgramCodes; }
    public void setValidProgramCodes(HashMap h) { validProgramCodes = h; }
}

たくさんのリーダースレッドがあり、それぞれがgetValidProgramCodes()を一度呼び出してから、そのハッシュマップを読み取り専用リソースとして使用します。

ここまでは順調ですね。ここが私たちが面白くなるところです。

有効なプログラムコードの新しいリストを頻繁に生成し(方法は気にしないでください)、setValidProgramCodesを呼び出すタイマーを設定したいと思います。

私の理論(検証するために助けが必要です)は、明示的な同期を行わなくても、コードをそのまま使用し続けることができるというものです。これは次のようになります。validProgramCodesが更新されるとき、validProgramCodesの値は常に適切です。これは、新しいハッシュマップまたは古いハッシュマップへのポインターです。 これは、すべてが左右される前提です。 古いハッシュマップを持っている読者は大丈夫です。解放するまでガベージコレクションされないため、古い値を引き続き使用できます。各リーダーは一時的なものです。それはすぐに死に、新しい価値を手に入れる新しいものに置き換えられます。

これは水を保持しますか?私の主な目標は、更新が行われていない圧倒的多数のケースで、コストのかかる同期とブロックを回避することです。更新は1時間に1回程度で、読者は常にちらつきます。

4

10 に答える 10

28

揮発性を使用

これは、あるスレッドが別のスレッドを気にする場合ですか? 次に、JMM FAQに答えがあります。

ほとんどの場合、一方のスレッドは他方が何をしているか気にしません。しかし、そうするとき、それが同期の目的です。

OP のコードは現状のままで安全であると言う人に対しては、次のことを考慮してください。 Java のメモリ モデルには、新しいスレッドの開始時にこのフィールドがメイン メモリにフラッシュされることを保証するものは何もありません。さらに、変更がスレッド内で検出されない限り、JVM は操作を自由に並べ替えることができます。

理論的に言えば、リーダー スレッドが有効なプログラム コードへの「書き込み」を確認できる保証はありません。実際には、最終的にはそうなりますが、いつになるかはわかりません。

validProgramCodes メンバーを「volatile」として宣言することをお勧めします。速度の違いはごくわずかであり、JVM の最適化がどのように導入されても、現在および将来のコードの安全性が保証されます。

具体的な推奨事項は次のとおりです。

import java.util.Collections;

class Metadata {

    private volatile Map validProgramCodes = Collections.emptyMap();

    public Map getValidProgramCodes() { 
      return validProgramCodes; 
    }

    public void setValidProgramCodes(Map h) { 
      if (h == null)
        throw new NullPointerException("validProgramCodes == null");
      validProgramCodes = Collections.unmodifiableMap(new HashMap(h));
    }

}

不変性

でラップするだけでなくunmodifiableMap、マップ ( ) をコピーしていますnew HashMap(h)。これにより、setter の呼び出し元がマップ "h" を更新し続けても変更されないスナップショットが作成されます。たとえば、マップをクリアして新しいエントリを追加する場合があります。

インターフェイスに依存

スタイル上の注意として、 and のような具象型ではなく、 and のような抽象型で API を宣言する方がよい場合がよくありますList。これにより、将来、具象型を変更する必要がある場合に柔軟性が得られます (ここで行ったように)。MapArrayListHashMap.

キャッシング

「h」を「validProgramCodes」に割り当てた結果は、単にプロセッサのキャッシュへの書き込みである可能性があります。新しいスレッドが開始された場合でも、共有メモリにフラッシュされていない限り、「h」は新しいスレッドに表示されません。適切なランタイムは、必要でない限りフラッシュを回避します。使用volatileは、フラッシュが必要であることを示す 1 つの方法です。

並べ替え

次のコードを想定します。

HashMap codes = new HashMap();
codes.putAll(source);
meta.setValidProgramCodes(codes);

setValidCodesが単に OP の場合validProgramCodes = h;、コンパイラは次のようにコードを自由に並べ替えることができます。

 1: meta.validProgramCodes = codes = new HashMap();
 2: codes.putAll(source);

ライター行 1 の実行後、リーダー スレッドが次のコードの実行を開始するとします。

 1: Map codes = meta.getValidProgramCodes();
 2: Iterator i = codes.entrySet().iterator();
 3: while (i.hasNext()) {
 4:   Map.Entry e = (Map.Entry) i.next();
 5:   // Do something with e.
 6: }

ここで、ライター スレッドがリーダーの行 2 と行 3 の間のマップで "putAll" を呼び出すとします。イテレーターの基になるマップで同時変更が発生し、実行時例外がスローされます。テスト中に生成されます。

並行プログラミング

別のスレッドが何をしているかを気にする 1 つのスレッドがあるときはいつでも、あるスレッドのアクションが他のスレッドから見えるようにするために、ある種のメモリ バリアが必要です。あるスレッドのイベントが別のスレッドのイベントの前に発生する必要がある場合は、明示的に示す必要があります。それ以外の保証はありません。実際には、これはvolatileまたはを意味しsynchronizedます。

ケチらないでください。正しくないプログラムがどれだけ速く仕事を失敗しても、問題ではありません。ここに示されている例は単純で不自然ですが、予測不可能でプラットフォームの影響を受けやすいため、特定して解決するのが非常に難しい現実世界の同時実行バグを示しているので安心してください。

その他のリソース

于 2008-11-18T22:14:56.850 に答える
4

いいえ、新しいHashMapインスタンスの安全な公開がないため、コード例は安全ではありません。同期がないと、リーダースレッドが部分的に初期化されたHashMapを見る可能性があります。

彼の答えの「並べ替え」の下にある@ericksonの説明をチェックしてください。また、BrianGoetzの著書「JavaConcurrencyinPractice」を十分にお勧めすることはできませ

リーダースレッドが古い(古い)HashMap参照を表示する可能性があるか、または新しい参照を表示しない可能性があるかどうかは、重要ではありません。発生する可能性のある最悪の事態は、リーダースレッドが、まだ初期化されておらず、アクセスする準備ができていないHashMapインスタンスへの参照を取得し、アクセスを試みる可能性があることです。

于 2008-11-20T07:23:56.023 に答える
3

いいえ、Java メモリ モデル (JMM) により、これはスレッドセーフではありません。

実装オブジェクトの書き込みと読み取りの間には、事前発生の関係はありません。HashMapそのため、ライター スレッドは最初にオブジェクトを書き込んでから参照を書き出すように見えますが、リーダー スレッドでは同じ順序で表示されない場合があります。

前述のように、リアスレッドが新しい値を認識するという保証はありません。実際には、既存のハードウェア上の現在のコンパイラでは、ループ本体が十分に小さくて十分にインライン化できない場合を除き、値を更新する必要があります。

したがって、volatile新しい JMM では参照を作成するだけで十分です。システムのパフォーマンスに大きな違いが生じる可能性はほとんどありません。

この話の教訓: スレッド化は難しい。賢くなろうとしないでください。(テスト システムにない場合もあります) 十分に賢くなれないことがあります。

于 2008-11-19T15:09:30.530 に答える
3

他の人がすでに指摘しているように、これは安全ではなく、これを行うべきではありません。他のスレッドに変更を強制的に表示させるには、ここで volatile または synchronized のいずれかが必要です。

言及されていないのは、同期された、特に揮発性がおそらくあなたが思っているよりもずっと速いということです。それが実際にアプリのパフォーマンスのボトルネックである場合は、この Web ページを食べます。

別のオプション (おそらく volatile よりも遅いですが、YMMV) は、ReentrantReadWriteLock を使用してアクセスを保護し、複数の同時リーダーがそれを読み取れるようにすることです。それでもパフォーマンスのボトルネックになる場合は、この Web サイト全体を使い果たします。

  public class Metadata
  {
    private HashMap validProgramCodes;
    private ReadWriteLock lock = new ReentrantReadWriteLock();

    public HashMap getValidProgramCodes() { 
      lock.readLock().lock();
      try {
        return validProgramCodes; 
      } finally {
        lock.readLock().unlock();
      }
    }

    public void setValidProgramCodes(HashMap h) { 
      lock.writeLock().lock();
      try {
        validProgramCodes = h; 
      } finally {
        lock.writeLock().unlock();
      }
    }
  }
于 2008-11-20T15:44:48.260 に答える
2

あなたの仮定は正しいと思います。私がする唯一のことは、validProgramCodes揮発性を設定することです。

private volatile HashMap validProgramCodes;

このように、「ポインター」を更新するとvalidProgramCodes、すべてのスレッドが同じ最新の「ポインター」にアクセスすることが保証されHasMapます。これは、ローカル スレッド キャッシュに依存せず、直接メモリに移動するためです。

于 2008-11-18T22:23:10.717 に答える
1

割り当ては、古い値を読み取ることを気にせず、初期化時にハッシュマップが適切に設定されていることを保証できる限り機能します。少なくとも Hashmap で Collections.unmodifiableMap を使用して hashMap を作成し、リーダーがマップからオブジェクトを変更/削除しないことを保証し、複数のスレッドが互いに足を踏み入れたり、他のスレッドが破棄されたときにイテレータを無効にしたりしないようにする必要があります。

(上記のライターは揮発性について正しいです、それを見るべきでした)

于 2008-11-18T22:18:15.313 に答える
1

これはこの特定の問題に対する最善の解決策ではありませんが (erickson の新しい unmodifiableMap のアイデアはそうです)、Java 5 で導入されたjava.util.concurrent.ConcurrentHashMapクラス、特に HashMap のバージョンについて言及したいと思います。並行性を念頭に置いて構築されています。この構造は読み取り時にブロックしません。

于 2008-11-21T13:45:39.360 に答える
0

同時実行の基本に関するこの投稿を確認してください。あなたの質問に十分に答えることができるはずです。

http://walivi.wordpress.com/2013/08/24/concurrency-in-java-a-beginners-introduction/

于 2013-08-28T11:44:32.313 に答える
-1

危険だと思います。スレッド化により、デバッグが非常に困難なあらゆる種類の微妙な問題が発生します。FastHashMapこのような読み取り専用スレッドのケースを対象とした をご覧になることをお勧めします。

少なくとも、参照がレジスタなどに最適化されないようにするvalidProgramCodesことも宣言します。volatile

于 2008-11-18T22:13:12.860 に答える
-3

JLSを正しく読めば(保証はありません!)、参照へのアクセスは常にアトミックでピリオドです。セクション 17.7 double と long の非アトミック処理を参照してください。

したがって、参照へのアクセスが常にアトミックであり、返さHashmapれたインスタンスがスレッドに表示されるかどうかが問題にならない場合は、問題ありません。参照への部分的な書き込みは見られません。


編集:以下のコメントでの議論と他の回答を確認した後、ここからの参照/引用があります

Doug Leaの本 (Java での並行プログラミング、第 2 版)、p 94、セクション 2.2.7.2可視性、項目 #3: "

スレッドがオブジェクトのフィールドに初めてアクセスすると、フィールドの初期値か、他のスレッドによって書き込まれた後の値が表示されます。」

p。94 で、Lea はこのアプローチに関連するリスクについて次のように説明しています。

メモリ モデルは、上記の操作が最終的に発生した場合、あるスレッドによって行われた特定のフィールドへの特定の更新が最終的に別のスレッドに表示されることを保証します。しかし、最終的には任意の長い時間になる可能性があります。

したがって、絶対に、積極的に、呼び出し元のスレッドから見える必要がある場合volatile、または他の同期バリアが必要な場合、特に長時間実行されるスレッドまたはループ内の値にアクセスするスレッドで (Lea が言うように)。

ただし、質問で示唆されているように、新しいリーダー用の新しいスレッドを備えた存続期間の短いスレッドがあり、古いデータを読み取るアプリケーションに影響を与えない場合、同期は必要ありませ ん。


@ ericksonの回答は、この状況で最も安全であり、他のスレッドが参照への変更を発生時に確認できることを保証しHashMapます。この回答と以下の議論で「反対票」を投じた要件と実装に関する混乱を避けるために、そのアドバイスに従うことをお勧めします。

役立つことを期待して回答を削除するつもりはありません。私は「同調圧力」バッジを探しているわけではありません... ;-)

于 2008-11-18T22:35:51.630 に答える