其他写的比较好的文章: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
分析
我们先看看这个错误是什么,下面是源码
/**
* 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的修改操作,可以试着将修改操作加锁。