15

最近、コードベースでこの宝石を見つけました:

/** This class is used to "publish" changes to a non-volatile variable.
 *
 * Access to non-volatile and volatile variables cannot be reordered,
 * so if you make changes to a non-volatile variable before calling publish,
 * they are guaranteed to be visible to a thread which calls syncChanges
 *
 */
private static class Publisher {
    //This variable may not look like it's doing anything, but it really is.
    //See the documentaion for this class.
    private volatile AtomicInteger sync = new AtomicInteger(0);

    void publish() {
        sync.incrementAndGet();
    }

    /**
     *
     * @return the return value of this function has no meaning.
     * You should not make *any* assumptions about it.
     */
    int syncChanges() {
        return sync.get();
    }
}

これは次のように使用されます。

スレッド 1

float[][] matrix;
matrix[x][y] = n;
publisher.publish();

スレッド 2

publisher.syncChanges();
myVar = matrix[x][y];

スレッド 1 は、継続的に実行されるバックグラウンド更新スレッドです。スレッド 2 は HTTP ワーカー スレッドであり、読み取った内容が何らかの形で一貫性があるか原子的であるかを気にせず、書き込みが「最終的に」そこに到達し、並行性の神への供物として失われないことだけを気にします。

さて、これは私のすべての警告ベルをトリガーします。無関係なコードの奥深くに記述されたカスタム同時実行アルゴリズム。

残念ながら、コードの修正は簡単ではありません。同時プリミティブ行列に対する Java サポートは適切ではありません。これを修正する最も明確な方法は を使用するように見えますがReadWriteLock、それはおそらくパフォーマンスに悪影響を及ぼすでしょう。正確さは明らかにより重要ですが、パフォーマンスに敏感な領域からそれを切り取る前に、これが正しくないことを証明する必要があるようです.

java.util.concurrent のドキュメントによると、次のようにhappens-before関係が作成されます。

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

揮発性フィールドへの書き込みは、同じフィールドの後続のすべての読み取りの前に発生します。volatile フィールドの書き込みと読み取りには、モニターの開始と終了と同様のメモリ整合性効果がありますが、相互排他ロックは必要ありません。

したがって、次のように聞こえます。

  • マトリックス書き込みは公開前に発生します() (ルール 1)
  • publish() は syncChanges() の前に発生 (ルール 2)
  • syncChanges() マトリックス読み取りの前に発生 (ルール 1)

したがって、コードは実際にマトリックスの事前発生チェーンを確立しています。

しかし、私は確信していません。並行性は難しく、私はドメインの専門家ではありません。私は何を逃したのですか?これは本当に安全ですか?

4

4 に答える 4

4

可視性の観点から、必要なのは、任意の揮発性フィールドでの揮発性書き込み-読み取りです。これはうまくいくでしょう

final    float[][] matrix  = ...;
volatile float[][] matrixV = matrix;

スレッド1

matrix[x][y] = n;
matrixV = matrix; // volatile write

スレッド2

float[][] m = matrixV;  // volatile read
myVar = m[x][y];

or simply
myVar = matrixV[x][y];

ただし、これは1つの変数を更新する場合にのみ有効です。ライタースレッドが複数の変数を書き込んでいて、読み取りスレッドがそれらを読み取っている場合、リーダーには一貫性のない画像が表示されることがあります。通常、これは読み取り/書き込みロックによって処理されます。コピーオンライトは、一部の使用パターンに適している場合があります。

Doug Leaには、Java8用の新しい「StampedLock」http://gee.cs.oswego.edu/dl/jsr166/dist/jsr166edocs/jsr166e/StampedLock.htmlがあります。これは、読み取りがはるかに安価なバージョンの読み取り/書き込みロックです。ロック。しかし、使用するのもはるかに困難です。基本的に、リーダーは現在のバージョンを取得し、次に進んで一連の変数を読み取り、バージョンを再度確認します。バージョンが変更されていない場合、読み取りセッション中に同時書き込みはありませんでした。

于 2013-02-05T19:22:31.473 に答える
4

これは、マトリックスへの単一の更新を発行するのに安全に見えますが、もちろん原子性は提供しません。それが問題ないかどうかはアプリケーションによって異なりますが、おそらくこのようなユーティリティ クラスで文書化する必要があります。

ただし、これには冗長性が含まれており、syncフィールドを にすることで改善される可能性がありますfinal。このvolatileフィールドへのアクセスは、2 つのメモリ バリアの最初のものです。契約により、呼び出しincrementAndGet()は揮発性変数の書き込みおよび読み取りと同じ効果をメモリにもたらし、呼び出しは読み取りget()と同じ効果をもたらします。

そのため、コードはこれらのメソッドのみによって提供される同期に依存し、フィールド自体を作成できますfinal

于 2013-02-05T19:53:29.767 に答える
2

を使用するvolatileことは、すべてを同期させる特効薬ではありません。別のスレッドが揮発性変数の更新された値を読み取った場合、それ以前に非揮発性変数に加えられたすべての変更も表示されることが保証されています。しかし、他のスレッドが更新された値を読み取るという保証はありません

サンプル コードで、 にいくつかの書き込みを行ってからmatrixを呼び出しpublish()、他のスレッドが を呼び出しsynch()てから行列を読み取ると、他のスレッドは変更の一部またはすべてを確認するか、変更をまったく確認しない可能性があります。

  • publish() から更新された値を読み取る場合、すべての変更
  • 古い公開された値を読み取り、変更が漏えいしていない場合、変更はありません
  • 以前に公開された値を読み取った場合、一部の変更がリークされていますが、一部の変更はリークされています

この記事を見る

于 2013-02-05T19:29:05.843 に答える
1

先行発生関係のルール #2 について正しく言及されています

揮発性フィールドへの書き込みは、同じフィールドの後続のすべての読み取りの前に発生します。

ただし、絶対タイムラインで syncChanges() の前に publish() が呼び出されることは保証されません。あなたの例を少し変えてみましょう。

スレッド 1:

matrix[0][0] = 42.0f;
Thread.sleep(1000*1000); // assume the thread was preempted here
publisher.publish(); //assume initial state of sync is 0 

スレッド 2:

int a = publisher.syncChanges();
float b = matrix[0][0];

a 変数と b 変数のオプションは何を利用できますか?

  • a は 0、b は 0 または 42 です
  • a は 1、b は 42 です。これは、先行発生の関係によるものです。
  • a は 1 より大きい (スレッド 2 は何らかの理由で遅く、スレッド 1 は更新を数回発行できて幸運でした)、b の値はビジネス ロジックとマトリックスの処理方法に依存します。以前の状態に依存するかどうか

それに対処する方法は?ビジネスロジックに依存します。

  • スレッド 2 が時々マトリックスの状態をポーリングし、その間に古い値があってもまったく問題ない場合、最終的に正しい値が処理される場合は、そのままにしておきます。
  • スレッド 2 が更新の欠落を気にせず、常に最新のマトリックスを観察したい場合は、コピー オン ライト コレクションを使用するか、前述のように ReaderWriteLock を使用します。
  • スレッド 2 が単一の更新を気にする場合は、よりスマートな方法で処理する必要があります。wait() / notify() パターンを検討し、マトリックスが更新されるたびにスレッド 2 に通知することをお勧めします。
于 2013-02-06T06:52:51.250 に答える