3

Javaの並行性とマップに関して少し問題があります。基本的に、私は独自のマップを使用(読み取りおよび変更)する複数のスレッドを持っていますが、これらのマップのそれぞれは、別のスレッドによって読み取られて変更されているより大きなマップの一部です。

私のメインメソッドはすべてのスレッドを作成し、スレッドはそれぞれのマップを作成し、それが「メイン」マップに配置されます。

Map<String, MyObject> mainMap = new HashMap<String, Integer>();
FirstThread t1 = new FirstThread();
mainMap.putAll(t1.getMap());
t1.start();
SecondThread t2 = new SecondThread();
mainMap.putAll(t2.getMap());
t2.start();
ThirdThread t3 = new ThirdThread(mainMap);
t3.start();

私が今直面している問題は、他のスレッドの一方または両方が「それらの」アイテムを更新するタイミングに応じて、3番目の(メイン)スレッドがマップ内の任意の値を参照することです。ただし、読み取られているものの一部が「古い」ことを恐れることなく、3番目のスレッドがマップを反復処理できること(およびマップの値を使用できること)を保証する必要があります。

FirstThread(SecondThreadのアナログ):

for (MyObject o : map.values()) {
    o.setNewValue(getNewValue());
}

ThirdThread:

for (MyObject o : map.values()) {
    doSomethingWith(o.getNewValue());
}

何か案は?マップを変更する必要があるときに各スレッドで同期される、グローバルにアクセス可能な(静的クラスを介した静的な最終オブジェクト)ロックを使用することを検討しました。または、私が使用できるこの特定の問題を評価する特定のMap実装はありますか?

前もって感謝します!

編集: @Pyranjaによって提案されたように、getNewValue()メソッドを同期することは可能です。しかし、私は実際にトランザクションのラインに沿って何かをしようとしていることを言及するのを忘れました。そこでは、t3が上記の値で動作する前/後にt1とt2が複数の値を変更します。t3は、値が変更されていない場合、doSomethingWith()が実際には値に対して何も実行しないように実装されています。

4

3 に答える 3

3

個々の値オブジェクトよりも高いレベルで同期するには、さまざまなスレッド間の同期を処理するためのロックが必要です。コードをあまり変更せずにこれを行う1つの方法は、ReadWriteLockです。スレッド1とスレッド2はライター、スレッド3はリーダーです。

これは、2つのロックまたは1つのロックのいずれかで実行できます。データ更新中の例外(つまり、トランザクションのロールバック...)で何が起こるかを気にせずに、1つのロック、2つのライタースレッド、および1つのリーダースレッドでそれを行うことを以下にスケッチしました。

とはいえ、これは古典的な生産者/消費者シナリオのように聞こえます。この質問で概説されているように、スレッド間の通信にはBlockingQueueのようなものを使用することを検討する必要があります。

Threadを拡張する代わりにRunnableを使用するなど、変更を検討したいことが他にもあります。

private static final class Value {

    public void update() {

    }

}

private static final class Key {

}

private final class MyReaderThread extends Thread {

    private final Map<Key, Value> allValues;

    public MyReaderThread(Map<Key, Value> allValues) {
        this.allValues = allValues;
    }

    @Override
    public void run() {
        while (!isInterrupted()) {
            readData();
        }
    }

    private void readData() {
        readLock.lock();
        try {
            for (Value value : allValues.values()) {
                // Do something
            }
        }
        finally {
            readLock.unlock();
        }

    }
}

private final class WriterThread extends Thread {

    private final Map<Key, Value> data = new HashMap<Key, Value>();

    @Override
    public void run() {
        while (!isInterrupted()) {
            writeData();
        }
    }

    private void writeData() {
        writeLock.lock();

        try {
            for (Value value : data.values()) {
                value.update();
            }
        }
        finally {
            writeLock.unlock();
        }
    }
}

private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

private final ReadLock readLock;
private final WriteLock writeLock;

public Thing() {
    readLock = lock.readLock();
    writeLock = lock.writeLock();
}

public void doStuff() {
    WriterThread thread1 = new WriterThread();
    WriterThread thread2 = new WriterThread();

    Map<Key, Value> allValues = new HashMap<Key, Value>();
    allValues.putAll(thread1.data);
    allValues.putAll(thread2.data);
    MyReaderThread thread3 = new MyReaderThread(allValues);

    thread1.start();
    thread2.start();
    thread3.start();
}
于 2013-01-30T22:49:15.617 に答える
2

ConcurrentHashMapfrom-同期マップよりjava.util.concurrentもはるかに高度な同時実行性を提供するMapのスレッドセーフな実装。ほとんどの場合、多くの読み取りを並行して実行できます。通常、読み取りと書き込みを同時に実行でき、複数の同時記録を並行して実行できることがよくあります。(このクラスConcurrentReaderHashMapは、複数の読み取り操作に対して同様の並列処理を提供しますが、アクティブな書き込み操作は1つしか許可しません。)ConcurrentHashMap検索操作を最適化するように設計されています。

于 2013-01-30T21:05:36.400 に答える
1

サンプルコードは誤解を招く可能性があります。最初の例では、を作成しますHashMap<String,Integer>が、2番目の部分は、この場合はであるマップ値を繰り返しますMyObject。同期の鍵は、どこでどの可変状態が共有されているかを理解することです。

AnIntegerは不変です。自由に共有できます(ただし、への参照Integer 変更可能です。安全に公開および/または同期する必要があります)。MyObjectしかし、あなたのコード例は、マップが可変インスタンスで占められていることを示唆しています。

マップエントリ(キー->MyObject参照)がスレッドによって変更されずすべてのマップが作成され、スレッドが開始する前に安全に公開されることを考えると、の変更を同期するのに十分だと思いMyObjectます。例えば:

public class MyObject {
   private Object value;

   synchronized Object getNewValue() {
      return value;
   }

   synchronized void setNewValue(final Object newValue) {
      this.value = newValue;
   }
}

私の仮定が正しくない場合は、質問/コード例を明確にし、@jacobmのコメントと@Alexの回答も検討してください。

于 2013-01-30T21:24:14.593 に答える