web-dev-qa-db-ja.com

java配列のスレッドセーフ

インデックスが異なる限り、あるスレッドが配列の1つのインデックスから読み取り、別のスレッドが配列の別のインデックスに書き込むという並行性の問題はありますか?

例えば(この例は、必ずしも実際の使用に推奨されるわけではなく、私の主張を説明するためだけのものです)

 class Test1
 {
     static final private int N = 4096;
     final private int[] x = new int[N];
     final private AtomicInteger nwritten = new AtomicInteger(0);
     // invariant: 
     // all values x[i] where 0 <= i < nwritten.get() are immutable

     // read() is not synchronized since we want it to be fast
     int read(int index) {
         if (index >= nwritten.get())
             throw new IllegalArgumentException();
         return x[index];
     }
     // write() is synchronized to handle multiple writers
     // (using compare-and-set techniques to avoid blocking algorithms
     // is nontrivial)
     synchronized void write(int x_i) {
         int index = nwriting.get();
         if (index >= N)
             throw SomeExceptionThatIndicatesArrayIsFull();
         x[index] = x_i;
         // from this point forward, x[index] is fixed in stone
         nwriting.set(index+1);
     }     
 }

編集:この例を批評することは私の質問ではありません。あるインデックスへの配列アクセスと同時に別のインデックスへのアクセスが並行性の問題を引き起こすかどうかを文字通り知りたいのですが、簡単な例を考えることができませんでした。

25
Jason S

あなたが言及したように配列を変更することによって無効な状態を取得することはありませんが、2つのスレッドが同期なしで不揮発性整数を表示しているときに発生する同じ問題が発生します(Java メモリ整合性エラー )に関するチュートリアル基本的に、問題はスレッド1がスペースiに値を書き込む可能性があることですが、スレッド2がいつ(またはいつ)変更を確認するかは保証されません。

クラス - Java.util.concurrent.atomic.AtomicIntegerArray やりたいことをします。

16
Kathy Van Stone

この例には、散文の質問とは異なるものがたくさんあります。

その質問に対する答えは、配列の個別の要素に個別にアクセスするため、2つのスレッドが異なる要素を変更する場合に同期をとる必要がないということです。

ただし、Javaメモリモデルは、アクセスを同期しない限り、あるスレッドによって書き込まれた値が別のスレッドに表示されることを保証しません(私が知っていることです)。

あなたが本当に達成しようとしていることに応じて、それはおそらくJava.util.concurrentすでにあなたのためにそれを行うクラスがあります。そうでない場合でも、ConcurrentHashMapのソースコードを確認することをお勧めします。これは、コードがハッシュテーブルの管理と同じことを実行しているように見えるためです。

4
kdgregory

writeメソッドのみを同期し、readメソッドを同期しないままにしておくと、うまくいくかどうかはよくわかりません。実際にはすべての結果が何であるかではありませんが、少なくともreadメソッドがwriteによってオーバーライドされたばかりの値を返す可能性があります。

2

はい。マルチCPU /コア環境でもキャッシュのインターリーブがうまくいかない可能性があるためです。それを回避するためのいくつかのオプションがあります。

  • Unsafe Sun-privateライブラリを使用して、配列内の要素をアトミックに設定します(または、Java7でjsr166yが追加した機能
  • AtomicXYZ []配列を使用する
  • 1つの揮発性フィールドを持つカスタムオブジェクトを使用し、そのオブジェクトの配列を用意します。
  • 代わりに、アルゴリズムでjsr166y補遺のParallelArrayを使用してください
2
akarnokd

Read()は同期されていないため、次のシナリオが考えられます。

Thread A enters write() method
Thread A writes to nwriting = 0;
Thread B reads from nwriting =0;
Thread A increments nwriting. nwriting=1
Thread A exits write();

変数アドレスが競合しないことを保証したいので、次のようなものはどうでしょうか(配列インデックスの問題の割引):

int i;
synchronized int curr(){ return i; }
synchronized int next(){ return ++i;}

int read( ) {
         return values[curr()];
     }

void write(int x){
   values[next()]=x;
}
0
Steve B.