5

コードは次のようになります。ここで使用されているマップは Guava マップです。

private Map<SomeObject, SomeOtherObject> myMap = Maps.newLinkedHashMap();

public Map<SomeObject, SomeOtherObject> getMap() {
  return Maps.newHashMap(myMap);
}

public void putMap(SomeObject a, SomeOtherObject b) {
  myMap.put(a,b);
}

したがって、上記はjava.util.ConcurrentModificationExceptionスローされ、シナリオを再現しようとしています。しかし、私が何をしようとしても、システムは回復力があるようです. これが私が試したものです:

 1. Created 'n' threads some of which call getMap() and some call putMap()
 2. Created 'n' threads that only call putMap() and one thread the is in an infinite loop that calls getMap()
 3. Created 2 threads each of which alternates calling getMap() and putMap(). Meaning, thread-1 first calls get then put and thread-2 first calls put and then get.

上記のいずれも機能せず、実行を続けるか、OOM に移行します。これを行う方法についての指針はありますか?

EDITマップ{ }ConcurrentModificationExceptionのコピーを返すときにスローされる と思います。Maps.newHashMap(myMap);このプロセス中にイテレータはコピーを作成し、イテレータが動作している間にマップの内容が変更された場合は満足できません。

4

6 に答える 6

2

実際に を使用すると仮定するとcom.google.common.collect.Maps、 の実装newHashMap

public static <K, V> HashMap<K, V> newHashMap(Map<? extends K, ? extends V> map) {
    return new HashMap<K, V>(map);
}

次に、の実装をHashMap見ると:

public HashMap(Map<? extends K, ? extends V> m) {
    this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
            DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
    putAllForCreate(m);
}

private void  [More ...] putAllForCreate(Map<? extends K, ? extends V> m) {
    for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i 
            = m.entrySet().iterator(); i.hasNext(); ) {
        Map.Entry<? extends K, ? extends V> e = i.next();
        putForCreate(e.getKey(), e.getValue());
    }
}

実際、 への呼び出しnewHashMapはイテレータを使用してマップをトラバースします。他の回答ですでに指摘されているようにputMap、マップの反復中に が呼び出された場合、これはConcurrentModificationException.

これをどのように再現できますか?2 つのスレッドで十分だと思います。1 つは を繰り返し呼び出しgetMap、もう 1 つは を呼び出しますputMap

于 2013-08-16T21:20:38.430 に答える
1

以下のコード サンプルは、数ミリ秒以内に "GOT IT" (つまり、ConcurrentModificationException) を出力するはずです。

実際には、コードはマップへの書き込みとマップからの取得を同時に行い、必然的に CME を取得します。ランダムな部分が重要です。同じキーを入れ続けると、同じ効果が得られない可能性があります。

public class CME {

    private static final Test test = new Test();
    private static final Random rand = new Random();

    public static void main(String[] args) throws InterruptedException {
        Runnable getter = new Runnable() {
            @Override public void run() {
                try {
                    while (!Thread.interrupted()) {
                        Map<String, String> tryit = test.getMap();
                    }
                } catch (ConcurrentModificationException e) {
                    System.out.println("GOT IT!");
                }
            }
        };
        Runnable putter = new Runnable() {
            @Override public void run() {
                while (!Thread.interrupted()) {
                    //use a random component to make sure the map
                    //is actually mutated
                    char c = (char) rand.nextInt();
                    test.putMap(String.valueOf(c), "whatever");
                }
            }
        };
        Thread g = new Thread(getter);
        Thread p = new Thread(putter);
        g.start();
        p.start();
        g.join(); //wait until CME
        p.interrupt(); //then interrupt the other thread
                       //to allow program to exit
    }

    static class Test {
        private Map<String, String> myMap = Maps.newLinkedHashMap();
        public Map<String, String> getMap() { return Maps.newHashMap(myMap); }
        public void putMap(String a, String b) { myMap.put(a, b); }
    }
}
于 2013-08-16T22:07:21.953 に答える
1

ConcurrentModificationExceptionCollectionまたはMapを使用して反復するときにスローさIteratorれ、反復中にCollectionが変更されます (ただし、必ずしも別のスレッドでは必要ありません)。

コピー操作で を使用するIterator場合、特に Map が小さい場合、反復がビジーである間に他のスレッドが Map を変更するという保証はありません。

この問題を強制するには、コピーを作成しているスレッドに、コピー ループ内からの変更スレッドを待機させる必要があります。Guava ライブラリを使用すると、それができない場合がありますが、一時的に手動コピーに置き換えると、問題を特定するのに役立ちます。

以下は、 CountDownLatches との問題の同期を強制するさまざまなスレッドを使用した基本的な例です。

public static void main(String[] args) {
    final Map<Integer, Integer> map = new HashMap<>();
    final CountDownLatch readLatch = new CountDownLatch(1);
    final CountDownLatch writeLatch = new CountDownLatch(1);

    for (int i = 0; i < 100; i++) {
        map.put(i, i);
    }

    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
                    if (entry.getKey().equals(Integer.valueOf(10))) {
                        try {
                            writeLatch.countDown();
                            readLatch.await();
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }).start();

    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                writeLatch.await();
                map.put(150, 150);
                readLatch.countDown();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }).start();
}

読み取りスレッドは、ある時点で書き込みスレッドを解放し、待機します。その間、書き込みスレッドは変更を行い、読み取りスレッドがループを再開できるようにします。

デバッグしている場合は、このようなラッチを挿入して問題を強制すると役立つ場合があります。同時実行性の問題を後で修正すると、ラッチが配置された状態でデッドロックする可能性があることに注意してください。問題を修正するために、 を使用して信頼できるエラーを取得できる可能性がありますsleep()。その後の修正は、単に確実に機能するはずです。

于 2013-08-16T21:04:55.993 に答える
0

本当に を作成したい場合はConcurrentModificationException、 が必要ですIterator

myMap.entrySet().iterator()myMap.keySet().iterator()、またはを呼び出して明示的に作成するかmyMap.values().iterator()、「foreach」ループでエントリ、キー、または値を反復処理して暗黙的に作成します。繰り返しながら、マップを変更します。

ConcurrentModificationException何かが反復されているときにコレクションが変更されると、Aがスローされます。そうしないと、反復子で未定義の動作が発生します。リンクされた Javadoc から:

この例外は、そのような変更が許可されていない場合に、オブジェクトの同時変更を検出したメソッドによってスローされる場合があります。たとえば、あるスレッドが Collection を変更しているときに、別のスレッドが Collection を反復処理することは一般的に許可されていません。一般に、反復の結果は、これらの状況では未定義です。一部の Iterator 実装 (JRE によって提供されるすべての汎用コレクション実装の実装を含む) は、この動作が検出された場合に、この例外をスローすることを選択する場合があります。これを行う反復子は、将来の不確定な時点で恣意的で非決定論的な動作を危険にさらすのではなく、迅速かつ明確に失敗するため、フェイルファスト反復子として知られています。

この例外は、オブジェクトが別のスレッドによって同時に変更されたことを常に示すわけではないことに注意してください。1 つのスレッドが、オブジェクトのコントラクトに違反する一連のメソッド呼び出しを発行すると、オブジェクトはこの例外をスローする可能性があります。たとえば、フェイルファスト反復子を使用してコレクションを反復処理しているときに、スレッドがコレクションを直接変更すると、反復子はこの例外をスローします。

于 2013-08-16T21:05:25.063 に答える
0

ConcurrentModificationException反復中にフェイルファスト コレクションが変更されるとスローされます。これを実現する最も簡単な方法は、たとえば次のとおりです。

ArrayList<String> list = new ArrayList<String>();
// put items in list
for(String s : list)
   list.remove(0);
于 2013-08-16T21:04:55.210 に答える