1

メソッドを呼び出し、m(int i)位置の配列の値を変更するスレッドが多数あるとしますi。次のコードは正しいですか、それとも競合状態がありますか?

public class A{
    private int []a =new int[N];
    private Semaphore[] s=new Semaphore[N];

    public A(){
        for(int i =0 ; i<N ; i++)
           s[i]=new Semaphore(1);
    }

    public void m(int i){
        s[i].acquire();
        a[i]++;
        s[i].release();
    }
}
4

1 に答える 1

11

コードは正しいです。両方とも作成する必要がありますが、競合状態は表示asれませんfinal。また、取得および解放する必要のあるロックを使用するたびに、try/ finalを使用する必要があります。

s[i].acquire();
try {
   a[i]++;
} finally {
   s[i].release();
}

ただし、配列を更新する場合、アイテムごとに個別のロックを設定する必要はありません。主なコストはメモリの更新と他のネイティブ同期であるため、単一のロックも同様に適切です。とはいえ、実際の操作がそうでない場合は、または他のオブジェクトint ++を使用する必要があります。SemaphoreLock

ただし、単純な操作の場合は、次のようなもので十分です。

// make sure it is final if you are synchronizing on it
private final int[] a = new int[N];
...

public void m(int i) {
   synchronized (a) {
      a[i]++:
   }
}

ブロッキングについて本当に心配している場合は、の配列AtomicIntegerが別の可能性ですが、プロファイラーが別の方法で指示しない限り、これでもやり過ぎのように感じます。

private final AtomicInteger[] a = new AtomicInteger[N];
...

public A(){
    for(int i = 0; i < N; i++)
       a[i] = new AtomicInteger(0);
}

public void m(int i) {
    a[i].incrementAndGet();
}

編集:

単一のロック、ロックの配列、配列、および配列を比較する簡単な愚かなテストプログラムを作成しました。結果は次のとおりです。synchronizedsynchronizedAtomicIntegerSemaphore

  • synchronizedint[]10617msで
  • synchronizedObject[]1827msのアレイ上
  • AtomicIntegerアレイ1414ms
  • Semaphoreアレイ3211ms

しかし、キッカーは、これが10スレッドで、それぞれが1,000万回の反復を実行していることです。確かに高速ですが、実際に何百万回もの反復を行わない限り、アプリケーションで目立ったパフォーマンスの向上は見られません。これが「時期尚早の最適化」の定義です。コードの複雑さ、バグの可能性の増加、デバッグ時間の追加、メンテナンスコストの増加などにお金を払うことなります。Knuthを引用するには:

小さな効率、たとえば約97%の時間は忘れておく必要があります。時期尚早の最適化は、すべての悪の根源です。

さて、OPがコメントで示唆しているように、それi++は彼/彼が保護している実際の操作ではありません。増分にはるかに時間がかかる場合(つまり、ブロッキングを増やす場合)、ロックの配列が必要になります。

于 2012-09-11T14:49:16.073 に答える