1. HashMap
1) 并发问题
HashMap的并发问题源于多线程访问HashMap时, 如果存在修改Map的结构的操作(增删, 不包括修改), 则有可能会发生并发问题, 表现就是get()操作会进入无限循环
public V get(Object key) { if (key == null) return getForNullKey(); Entry<K,V> entry = getEntry(key); return null == entry ? null : entry.getValue(); }
final Entry<K,V> getEntry(Object key) { if (size == 0) { return null; } int hash = (key == null) ? 0 : hash(key); for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } return null; }
究其原因, 是因为 getEntry 先获取了 table 中的链表, 而链表是一个循环链表, 所以进入了无限循环, 在正常情况下, 链表并不会出现循环的情况
出现这种情况是在多线程进行put的时候, 因为put会触发resize(rehash)操作, 当多个rehash同时发生时, 链表就有可能变得错乱, 变成一个循环链表
void addEntry(int hash, K key, V value, int bucketIndex) { if ((size >= threshold) && (null != table[bucketIndex])) { resize(2 * table.length); hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } createEntry(hash, key, value, bucketIndex); } void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } Entry[] newTable = new Entry[newCapacity]; transfer(newTable, initHashSeedAsNeeded(newCapacity)); // transfer 方法对所有Entry进行了rehash table = newTable; threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); } void transfer(Entry[] newTable, boolean rehash) { int newCapacity = newTable.length; for (Entry<K,V> e : table) { while(null != e) { Entry<K,V> next = e.next; if (rehash) { e.hash = null == e.key ? 0 : hash(e.key); } int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } } }
多线程resize的时候会同时创建多个newTable, 然后同时rehash, 造成链表错乱
另外rehash对于hashmap的性能代价也是相当大的, 所以选择一个合适的table长度也是很重要的
2) iterator 与 fail-fast
遍历的两种方法
for (int i = 0; i < collection.size(); i++) { T t = collection.get(i) // ... } for (T t : collection) { // ... }
为什么使用iterator, 是因为有的数据结构 get(i) 的效率是O(n), 而非O(1), 例如 LinkedList, 那么整个循环的效率则会变为 O(n2)
iterator内部使用fail-fast机制来提醒并发问题的发生, 例如在遍历的时候同时修改map, 则会抛出ConcurrentModificationException异常
for (Entry<K, V> t : map) { map.remove(t.key); // Exception throw }
之所以抛出异常是因为
2. ConcurrentHashMap
HashMap 与 ConcurrentHashMap,布布扣,bubuko.com
原文:http://www.cnblogs.com/zemliu/p/3669834.html