首页 > 其他 > 详细

HashMap 与 ConcurrentHashMap

时间:2014-04-18 04:41:44      阅读:915      评论:0      收藏:0      [点我收藏+]

1. HashMap

1) 并发问题

HashMap的并发问题源于多线程访问HashMap时, 如果存在修改Map的结构的操作(增删, 不包括修改), 则有可能会发生并发问题, 表现就是get()操作会进入无限循环

bubuko.com,布布扣
    public V get(Object key) {
        if (key == null)
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);

        return null == entry ? null : entry.getValue();
    }
bubuko.com,布布扣
bubuko.com,布布扣
    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;
    }
bubuko.com,布布扣

究其原因, 是因为 getEntry 先获取了 table 中的链表, 而链表是一个循环链表, 所以进入了无限循环, 在正常情况下, 链表并不会出现循环的情况

出现这种情况是在多线程进行put的时候, 因为put会触发resize(rehash)操作, 当多个rehash同时发生时, 链表就有可能变得错乱, 变成一个循环链表

bubuko.com,布布扣
    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;
            }
        }
    }
bubuko.com,布布扣

多线程resize的时候会同时创建多个newTable, 然后同时rehash, 造成链表错乱

另外rehash对于hashmap的性能代价也是相当大的, 所以选择一个合适的table长度也是很重要的

2) iterator 与 fail-fast

 遍历的两种方法

bubuko.com,布布扣
for (int i = 0; i < collection.size(); i++) {
    T t = collection.get(i) 
    // ...
}

for (T t : collection) {
   // ...
}
bubuko.com,布布扣

 

为什么使用iterator, 是因为有的数据结构 get(i) 的效率是O(n), 而非O(1), 例如 LinkedList, 那么整个循环的效率则会变为 O(n2)

iterator内部使用fail-fast机制来提醒并发问题的发生, 例如在遍历的时候同时修改map, 则会抛出ConcurrentModificationException异常

bubuko.com,布布扣
for (Entry<K, V> t : map) {
    map.remove(t.key);
    // Exception throw
}
bubuko.com,布布扣

 

之所以抛出异常是因为

 

2. ConcurrentHashMap

HashMap 与 ConcurrentHashMap,布布扣,bubuko.com

HashMap 与 ConcurrentHashMap

原文:http://www.cnblogs.com/zemliu/p/3669834.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!