其他寫的比較好的文章:https://www.cnblogs.com/snowater/p/8024776.html
我在一次多線程讀寫map的時候,然後再周遊的時候也遇到了該問題。
現場代碼
private ConcurrentHashMap<Long, Set<Long>> m = new ConcurrentHashMap<>();
// 多線程運作中
public void test(Long p, Long value) {
Set<Long> s = new HashSet<>();
if (m.contains(p)) {
s = m.get(p);
s.add(value);
} else {
s.add(value);
}
m.put(p, s);
for (Long id: s) {
logger.info("" + id);
}
}
可以看到,我是在多線程的讀寫一個線程安全的Map,但我用一個Set去讀的map,這個Set可不是線程安全的,再之後的周遊Set的時候,就報了 java.util.ConcurrentModificationException 的錯。
分析
我們先看看這個錯誤是什麼,下面是源碼
/**
* The number of times this HashMap has been structurally modified
* Structural modifications are those that change the number of mappings in
* the HashMap or otherwise modify its internal structure (e.g.,
* rehash). This field is used to make iterators on Collection-views of
* the HashMap fail-fast. (See ConcurrentModificationException).
*/
transient int modCount;
final class KeySet extends AbstractSet<K> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
public final Iterator<K> iterator() { return new KeyIterator(); }
public final boolean contains(Object o) { return containsKey(o); }
public final boolean remove(Object key) {
return removeNode(hash(key), key, null, false, true) != null;
}
public final Spliterator<K> spliterator() {
return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super K> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.key);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
這個是源代碼,我們可以看到裡面有個modCount來表示修改次數,每次對HashMap的操作都會增加modCount,如果在周遊的時候,發現目前的modCount和周遊的modCount不一緻的時候,就會報錯。
在我遇到的場景,Set的底層實際上就是用的Map的Key來做實作的,我的Set并不是一個線程安全的,而且還是一個淺拷貝(指向同一個位址),是以在多線程周遊Set的時候,會出現modCount不一緻的問題,導緻報錯。
解決辦法
因為避免不了淺拷貝,是以我的解決辦法是将set替換成線程安全的,例如 ConcurrentHashMap,也可以是線程安全的Collection。
其他情況的解決辦法
将報錯的容器替換成線程安全的,例如萬能的 ConcurrentHashMap;關注任何map的修改操作,可以試着将修改操作加鎖。