2016-07-11 14 views
3

Javaで並行ビットセットを作成しようとしていますが、これはサイズの拡張を可能にします(固定長ではなく、非常に簡単です)。クラスの中核部分です(他のメソッドは現在重要ではありません)ConcurrentBitSetの競合条件

public class ConcurrentBitSet { 

static final int CELL = 32; 
static final int MASK = CELL - 1; 
final AtomicReference<AtomicIntegerArray> aiaRef; 

public ConcurrentBitSet(int initialBitSize) { 
    aiaRef = new AtomicReference<>(new AtomicIntegerArray(1 + ((initialBitSize - 1)/CELL))); 
} 

public boolean get(int bit) { 
    int cell = bit/CELL; 
    if (cell >= aiaRef.get().length()) { 
     return false; 
    } 
    int mask = 1 << (bit & MASK); 
    return (aiaRef.get().get(cell) & mask) != 0; 
} 

public void set(int bit) { 
    int cell = bit/CELL; 
    int mask = 1 << (bit & MASK); 
    while (true) { 
     AtomicIntegerArray old = aiaRef.get(); 
     AtomicIntegerArray v = extend(old, cell); 
     v.getAndAccumulate(cell, mask, (prev, m) -> prev | m); 
     if (aiaRef.compareAndSet(old, v)) { 
      break; 
     } 
    } 
} 

private AtomicIntegerArray extend(AtomicIntegerArray old, int cell) { 
    AtomicIntegerArray v = old; 
    if (cell >= v.length()) { 
     v = new AtomicIntegerArray(cell + 1); 
     for (int i = 0; i < old.length(); i++) { 
      v.set(i, old.get(i)); 
     } 
    } 
    return v; 
} 

public String toString() { 
    StringBuilder sb = new StringBuilder(); 
    for (int i = 0; i < aiaRef.get().length(); i++) { 
     for (int b = 0; b < CELL; b++) { 
      sb.append(get(i * CELL + b) ? '1' : '0'); 
     } 
    } 
    return sb.toString(); 
} 

} 

残念ながら、ここに競合状態があるようです。

ここでは、毎回数ビット失敗したサンプルテストコードです。ビット300まですべてのビットを出力する必要がありますが、毎回別の場所にランダムゼロがありません。私がほんの少ししか得ていない1台のPC、他には奇数/偶数の位置に8-10​​個のゼロがあります。 (複雑な何かが競合状態が消えるように傾向がある)のデバッグに

final ConcurrentBitSet cbs = new ConcurrentBitSet(10); 
    CountDownLatch latch = new CountDownLatch(1); 
    new Thread() { 
     public void run() { 
      try { 
       latch.await(); 
       for (int i = 0; i < 300; i += 2) { 
        cbs.set(i); 
       } 
      } catch (InterruptedException e) { 

      } 
     }; 
    }.start(); 
    new Thread() { 
     public void run() { 
      try { 
       latch.await(); 
       for (int i = 0; i < 300; i += 2) { 
        cbs.set(i + 1); 
       } 
      } catch (InterruptedException e) { 
      } 
     }; 
    }.start(); 
    latch.countDown(); 
    Thread.sleep(1000); 
    System.out.println(cbs.toString()); 

私が取得しています何の例は 11111111111111111111111111111111111111111111111111111101111111111111111111111111011111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111101111111111111111111111111111111111111111111111111111111111111011111111111111111111111111111111111111111111111111100000000000000000000 11111111111111111111111111111111111111110111111111111111111111110101111111111111111111110101011111111111111111111111111111111111010101111111111111111111111111111111111101010111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111100000000000000000000

それは難しいですが、それは2つのスレッドが試みる時点でのように見えますループの次の部分のaiaRef.get()からの破損したデータでループが終了している間に、同じサイズで配列のサイズを同時に拡張することができます(すでにアクセスした部分それを拡張しようとしている)は内部でいくつかのゼロを持つことになります。

誰でもバグはどこですか?

+1

2つのスレッドが処理を完了するのを1秒間待つのではなく、主スレッド '.join()'をそれぞれ持っている方がずっとスマートになります。そうすれば、あなたはどちらも終わったことを知ることができます。 'sleep()'では、オペレーティングシステム内のいくつかの不具合が1つまたは複数のものを遅らせることはないという保証はありません。 –

+0

@ jameslargeはい、実際には、現実の世界で生のスレッドを使用して以来、年を重ねていました(最近ではいつでもFuture、Futureです)。 –

答えて

4

問題がAtomicReferenceの唯一の仕事は、その参照のアトミック性を保護することであるためaiaRef.compareAndSet()のみ、同時交換に対する保護されていることです。アレイが再構築されている間に、参照されたオブジェクトが同時にが変更されたである場合、同じ参照をそれ自身と比較しているため、compareAndSet()が成功しますが、修正が失われている可能性があります。

+0

ありがとう、これは問題のようです。残念なことに、より良いソリューションを提供しようとすると、リエントラント・ロックを大量に読み書きすることができます。私は大規模なホットスポットであることが判明するまで、おそらくRWロックを維持するでしょう。 –

関連する問題