深入剖析 Java 7 中的 HashMap 和 ConcurrentHashMap(4)

不过要注意,其使用的 Java 版本既不是 7,也不是 8。在 Java7 中方法 addEntry() 添加节点到链表中是先扩容后再添加,而例子中的源码是:

void addEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; // 先添加节点 table[bucketIndex] = new Entry<K,V>(hash, key, value, e); // 然后扩容 if (size++ >= threshold) resize(2 * table.length); }

所以在 Java7 中此例子无效。而在 Java8 中,通过确保建新链与旧链的顺序是相同的,即可避免产生死循环。

4 HashMap遍历方式 import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class HashMapTest { private final static Map<Integer, Object> map = new HashMap<Integer, Object>(10000); private static final Object PRESENT = new Object(); public static void main(String args[]) { long startTime; long endTime; long totalTime; for (int i = 0; i < 7500; i++) { map.put(i, PRESENT); } // 方法一 startTime = System.nanoTime(); Iterator iter1 = map.entrySet().iterator(); while (iter1.hasNext()) { Map.Entry<Integer, Object> entry = (Map.Entry) iter1.next(); Integer key = entry.getKey(); Object val = entry.getValue(); } endTime = System.nanoTime(); totalTime = endTime - startTime; System.out.println("methor1 pays " + totalTime + " ms"); // 方法二 startTime = System.nanoTime(); Iterator iter2 = map.keySet().iterator(); while (iter2.hasNext()) { Object key = iter2.next(); Object val = map.get(key); } endTime = System.nanoTime(); totalTime = endTime - startTime; System.out.println("methor2 pays " + totalTime + " ms"); } }

运行结果:

深入剖析 Java 7 中的 HashMap 和 ConcurrentHashMap

5 性能对比

线程安全的使用 HashMap 有三种方式,分别为 Hashtable、SynchronizedMap()、ConcurrentHashMap。

Hashtable

使用 synchronized 来保证线程安全,几乎所有的 public 的方法都是 synchronized 的,而有些方法也是在内部通过 synchronized 代码块来实现。

synchronizedMap()

通过创建一个线程安全的 Map 对象,并把它作为一个封装的对象来返回。

ConcurrentHashMap

支持多线程对 Map 做读操作,并且不需要任何的 blocking 。这得益于 CHM 将 Map 分割成了不同的部分,在执行更新操作时只锁住一部分。根据默认的并发级别, Map 被分割成 16 个部分,并且由不同的锁控制。这意味着,同时最多可以有 16个 写线程操作 Map 。试想一下,由只能一个线程进入变成同时可由 16 个写线程同时进入(读线程几乎不受限制),性能的提升是显而易见的。但由于一些更新操作,如 put(), remove(), putAll(), clear()只锁住操作的部分,所以在检索操作不能保证返回的是最新的结果。

在迭代遍历 CHM 时, keySet 返回的 iterator 是弱一致和 fail-safe 的,可能不会返回某些最近的改变,并且在遍历过程中,如果已经遍历的数组上的内容变化了,不会抛出 ConcurrentModificationExceptoin 的异常。

什么时候使用 ConcurrentHashMap ?

CHM 适用于读者数量超过写者时,当写者数量大于等于读者时,CHM 的性能是低于 Hashtable 和 synchronizedMap 的。这是因为当锁住了整个 Map 时,读操作要等待对同一部分执行写操作的线程结束。

CHM 适用于做 cache ,在程序启动时初始化,之后可以被多个请求线程访问。

CHM 是Hashtable一个很好的替代,但要记住, CHM 的比 Hashrable 的同步性稍弱。

6 拓展:Java8 HashMap & ConcurrentHashMap

Java8 对 HashMap 和 ConcurrentHashMap 做了一些修改:

二者均利用了红黑树,所以其数据结构由 数组 + 链表 + 红黑树 组成。我们知道,链表上的数据需要一个一个比较下去才能找到我们需要的,时间复杂度取决于链表的长度,为 O(n)。为了降低这一部分的开销,在 Java8 中,当链表中的元素超过了 8 个以后,会将链表转换为红黑树,这个时候时间复杂度就降为了 O(logN)

Java8 中 ConcurrentHashMap 摒弃 Java7 中的 Segment 的概念,使用了另一种方式实现保证线程安全。

Linux公社的RSS地址:https://www.linuxidc.com/rssFeed.aspx

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/b40da9553d125ecc65b680b112e480e0.html