ConcurrentModificationExceptionを取り除く

ご存じのとおり、ConcurrentModificationExceptionはマルチスレッドとは関係ありません。 これは、コレクションを繰り返し処理しながらコレクションを変更しようとすると発生します。 いつものように、これには歴史的なルーツがあります:コレクションとイテレータはJava 1.2に登場し、当時はコレクションをトラバースするときにイテレータを明示的に使用することを避けることは不可能だったため、イテレータメソッドを使用してコレクションを変更する提案は完全にひどく見えませんでした:



Iterator iterator = collection.iterator(); while (iterator.hasNext()) { Object element = iterator.next(); if (iDontLikeThisElement(element)) { iterator.remove(); } }
      
      





いいえ、まだ見えました。 しかし、他のオプションはありませんでした。 5番目のJavaの後半では、foreachループが表示され、反復子の使用は主に暗黙的になります。



 for (E element : collection) { if (iDonLikeThisElement(element)) { collection.remove(element); // ! ConcurrentModificationException! } }
      
      





「あなたが欲しいものを見てください! 明示的なイテレータ、親愛なるカスタムマーを使用し、目立たないでください」-おそらくJavaプラットフォームの開発者がトップ5に取り組んでいたようなものです。



6番目のJavaでは、競争パッケージが表示されます。 これを行うことができます:



 Set<String> set = Collections.newSetFromMap(new ConcurrentHashMap<>());
      
      





そして、ConcurrentModificationExceptionをスローしないセットを取得します。 しかし、再び、幸福は完全に完全ではありません。



  1. 通常、マルチスレッドはまったく必要ありません。
  2. ヌルは、要素、キー、値としてサポートされていません。 まあ、正直に言うと。
  3. 要素の順序は定義されておらず、変更される可能性があります-これははるかに悪いです。 つまり 要素を実行し、精度を落として計算を実行すると、同じデータセットでの不快な驚きやさまざまな結果が待っている可能性があります。 元のデータの順序を正確に保持することが望ましいタスクもあります。 まあ、そのようなものには次のような場所もあります:
 set.add("aaa"); set.add("bbb"); for (String s : set) { set.add("ddd"); System.out.println(s); }
      
      





おわりに
aaa

bbb

ddd

 set.add("aaa"); set.add("bbb"); for (String s : set) { set.add("ccc"); System.out.println(s); }
      
      





おわりに
aaa

bbb

したがって、明確な順序で独自のコレクションを作成します。 取得したいもの:



  1. 1つのスレッド内で、アクションなしでいつでも要素を追加および削除できます。 そしてもちろん、一定の時間。
  2. 突然したい場合は、nullを格納できます。
  3. 要素は、追加された順にバイパスされます。


これはすべて、わずかに変更された双方向リストを使用して簡単に実現できます。



  1. 要素を削除しても、次の要素へのリンクは無効になりません。つまり、イテレータがこの要素上にある場合、さらに先に進むことができます。
  2. リストの最後に偽の要素を配置します。これは、リストに何かが追加されると実際の要素に変わります。 つまり リストの最後に到達しても、イテレータはnullに置かれず、新しい要素がコレクションに表示されても機能し続けます。 さらにコードでは、この偽のアイテムはプレースホルダーと呼ばれます。


写真を見てみましょう。







  1. 最初に、要素A、B、C、Dがあります。
  2. 次に、要素CとDが削除されます。
  3. 新しいアイテムEが追加されます。


削除時に要素Cを指すイテレータがあった場合、リンクをさらに移動すると、新しく追加された要素Eに到達します。イテレータがなかった場合、ガベージコレクタは削除された要素からメモリを解放できません。



さて、一定のアクセス時間のために、明らかにハッシュマップが必要です:



 import java.util.AbstractSet; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; public class LinkedSet<E> extends AbstractSet<E> { private static class LinkedElement<E> { E value; boolean exists; LinkedElement<E> prev; LinkedElement<E> next; } private Map<E, LinkedElement<E>> map = new HashMap<>(); private LinkedElement<E> placeholder = new LinkedElement<>(); private LinkedElement<E> head = placeholder; @Override public boolean isEmpty() { return head == placeholder; } @Override public int size() { return map.size(); } @Override public boolean contains(Object o) { return map.containsKey(o); } //     , ,  }
      
      





アイテムの追加:



  @Override public boolean add(E e) { LinkedElement<E> element = map.putIfAbsent(e, placeholder); if (element != null) { return false; } element = placeholder; element.exists = true; element.value = e; placeholder = new LinkedElement<>(); placeholder.prev = element; element.next = placeholder; return true; }
      
      





取り外し:



  @Override public boolean remove(Object o) { LinkedElement<E> removedElement = map.remove(o); if (removedElement == null) { return false; } removeElementFromLinkedList(removedElement); return true; } private void removeElementFromLinkedList(LinkedElement<E> element) { element.exists = false; element.value = null; element.next.prev = element.prev; if (element.prev != null) { element.prev.next = element.next; element.prev = null; } else { head = element.next; } }
      
      





イテレーター:



  @Override public Iterator<E> iterator() { return new ElementIterator(); } private class ElementIterator implements Iterator<E> { LinkedElement<E> next = head; LinkedElement<E> current = null; LinkedElement<E> findNext() { LinkedElement<E> n = next; while (!n.exists && n.next != null) { next = n = n.next; } return n; } @Override public boolean hasNext() { return findNext().exists; } @Override public E next() { LinkedElement<E> n = findNext(); if (!n.exists) { throw new NoSuchElementException(); } current = n; next = n.next; return n.value; } @Override public void remove() { if (current == null) { throw new IllegalStateException(); } if (map.remove(current.value, current)) { removeElementFromLinkedList(current); } else { throw new NoSuchElementException(); } } }
      
      





これを行うことができます:



 Set<Integer> set = new LinkedSet<>(); // ... put some numbers set.stream().filter(v -> v % 2 == 0).forEach(set::remove);
      
      





LinkedMapが同じ方法で構築できることは明らかです。 基本的にはこれですべてです。別の自転車が用意されています。 LinkedHashMapとLinkedHashSetライブラリがこのように機能しなかったのはなぜですか? ジャビストがジャバスクリプト主義者をen望している可能性があります。



All Articles