在多线程情况下,遍历map集合可能出现错误,原因是集合(Map和List\Set都一样)中的fail-fash机制,只要集合结构有变动时就出抛出异常。
Map源代码查看如下:
final Entry<K,V> nextEntry() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); ...
modCount是map定义的属性,保存结构被修改的次数,源代码可以看到在put和remove(Object)方法中都对modCount进行了加1操作:
public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; }
final Entry<K,V> removeEntryForKey(Object key) { int hash = (key == null) ? 0 : hash(key.hashCode()); int i = indexFor(hash, table.length); Entry<K,V> prev = table[i]; Entry<K,V> e = prev; while (e != null) { Entry<K,V> next = e.next; Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { modCount++; size--; if (prev == e) table[i] = next; else prev.next = next; e.recordRemoval(this); return e; } prev = e; e = next; } return e; }
Fail-Fast机制主要是为了避免正在处理的集合被修改而导致的一系列可能的问题,但在多线程环境下必须控制程序不引起正在被遍历的集合被修改。
解决的办法有两种。
一是进行同步加锁,保证同一时间只有一个线程能操作这个集合对象。对于Map的话在业务适合的情况可以使用HashTable可以保证同步。
第二种办法是在遍历集合之前,先对集合进行克隆,再操作此克隆对象。如对于Map集合map,可以使用Map cloneMap=new HashMap();cloneMap.putAll(map);进行浅克隆,此后遍历cloneMap集合。