4

技術的でないタイトルで申し訳ありませんが、それは私の質問をうまく要約していると思います。私が正しく読んだものを解釈すると、同期ブロックは(他の結果を除いて)すべての変数をメインメモリとの間で更新します(同期ブロック内で明示的にアクセスされていない変数でも、それらの「親」のみ?) 。たとえば、このスタックオーバーフローの質問の答えを引用します(私はそれを文脈から外しました、後でそれに戻ります):

メモリバリアは、無関係なものも含め、すべてのメモリ参照に適用されます。

これを正しく解釈できるかどうかの確認が必要です。私は2つのスレッド(threadA、threadB)を持っています。次のコードを検討してください。

public class SomeClass {

private final Object mLock = new Object();
private int[] anArray;

public void initA() {
  synchronized(mLock) {
     ...
     anArray = new int[...];
     operationA();
  }
}

public void operationA() {
  synchronized(mLock) {
      // Manipulating the ELEMENTS of anArray,
      // e.g. in loops, etc.
      anArray[i] = ...
  }
}

public int[] getterB() {
   synchronized(mLock) {
      return anArray;   
   }
}
}

getterB()ThreadBから呼び出され、ThreadAinitA()から呼び出さoperationA()れます。(これは、 ThreadBが作成される前でも呼び出されるため、同時実行されることに注意してください。)また、配列のコピーを返さないのには十分な理由があることに注意してください(いいえ、threadBはその要素を変更したくないので、理由は、現在は関係のない私のソフトウェアの外部要件です)。initA()getterB()operationA()getterB()

threadBはこれを行います:

int[] anArray = aSomeClass.getterB(); // aSomeClass is an instance of SomeClass
if (anArray[i] == n) { ....... } // various operations
...
//  various other operations that read the elements of anArray

ご覧のとおり、ではgetterB()、参照のみがanArrayメモリバリア内でアクセスされ、配列値自体にはアクセスされません。私の質問:

  1. threadBは最新の配列要素値を参照しますか?(つまり、要素自体もメインメモリから更新されていgetterB()ますか?)

  2. 引用されたステートメントは、無関係のキャッシュされたコピーがメインメモリからも更新されると述べています。私はこれを100%無関係に解釈する方法ではありません(ロックに使用される変数とは無関係ですか?または同期されたブロック全体とは無関係ですか?)。私は引用を文脈から外したことを知っています、そしてそれは別のスタックオーバーフローの質問なので、そこにコメントを追加しました。ですから、私の質問がそこで(またはここで-私は気にしません)答えられれば幸いです。

  3. anArrayオブジェクトの配列(プリミティブ型ではない)の場合、答えに違いはありますか?さらに進んで、それが配列ではなく、他のクラスへの参照を含むクラスである場合はどうなるでしょうか。(つまり、他のオブジェクトを参照するオブジェクトであり、によって返されたオブジェクトを介して含まれているオブジェクトにアクセスしますgetterB())。threadBは、これらの含まれている参照の最新のコピーを使用しますか、それとも 独自のローカルキャッシュコピーを使用しますか(コンテナオブジェクトのみを更新し、含まれている参照自体は更新しないため)。getterB()

4

1 に答える 1

4

順番に質問をする:

  1. はい:以前に呼び出された値から変更されたすべての値はoperationA()、の結果によって参照される配列内で「最新」であると安全に想定できますgetterB()

  2. 私はそれを他のリンクで答えさせます。私はまだそのリンクを読んでいないことを告白します。しかし、私の理解では、同期ブロックに出入りするときに、保留中のすべてのメモリの書き戻しが「効果的に」発生します(ただし、これがどのように発生するかの詳細、つまり、これの効率、およびより多くのキャッシング/パイプラインがあるかどうか)そのように見えるようにするための「トリック」は、ハードウェアとコンパイラに依存します)。これについてさらに詳しく知りたい場合は、このリンクが便利だと思ったことがあります:http: //www.infoq.com/articles/memory_barriers_jvm_concurrency

  3. いいえ、違いはありません(回答2で書いたことを考えると)。

getterB()最後に、配列のコピーを返さないという事実のために、上記で要約したように、私はあなたのコードに非常に警戒するだろうというコメントだけです。上記の方法でそれを行う理由があることを理解しています(そして、この種のフィードバックを望まなかったのです!)が、スレッド内のすべての「さまざまな操作」が後に発生することを必ず理解する必要がBありanArrayますgetterB()戻ってきたものは保護されません。(言い換えると、Aこの間にスレッドで行われた配列への変更には危険が伴います。)非効率的な深い配列コピーを回避する1つの代替方法は、これらの「さまざまな操作」を新しいメソッド内の同期ブロック内に移動することです。SomeClassと取り除くgetterB()全体的に。もちろん、あなたのコードの「正しい解決策」はここに示されていない多くのものに依存していることを私は理解しているので、このビットを無視してかまいません。

于 2012-07-01T01:45:47.680 に答える