map的线程安全问题

为何HashMap是线程不安全的

总说 HashMap 是线程不安全的,不安全的,不安全的,那么到底为何它是线程不安全的呢?要回答这个问题就要先来简单了解一下 HashMap 源码中的使用的存储结构(这里引用的是 Java 8 的源码,与7是不同的)和它的扩容机制java

HashMap 内部存储使用了一个 Node 数组(默认大小是16),而 Node 类包含一个类型为 Node 的 next 的变量,也就是至关于一个链表,全部根据 hash 值计算的 bucket 同样的 key 会存储到同一个链表里(即产生了冲突)。算法

 

HashMap的自动扩容机制

HashMap 内部的 Node 数组默认的大小是16,假设有100万个元素,那么最好的状况下每一个 hash 桶里都有62500个元素,这时get(),put(),remove()等方法效率都会下降。为了解决这个问题,HashMap 提供了自动扩容机制,当元素个数达到数组大小 loadFactor 后会扩大数组的大小,在默认状况下,数组大小为16,loadFactor 为0.75,也就是说当 HashMap 中的元素超过16\0.75=12时,会把数组大小扩展为2*16=32,而且从新计算每一个元素在新数组中的位置。编程

为何线程不安全

我的以为 HashMap 在并发时可能出现的问题主要是两方面,首先若是多个线程同时使用put方法添加元素,并且假设正好存在两个 put 的 key 发生了碰撞(根据 hash 值计算的 bucket 同样),那么根据 HashMap 的实现,这两个 key 会添加到数组的同一个位置,这样最终就会发生其中一个线程的 put 的数据被覆盖。第二就是若是多个线程同时检测到元素个数超过数组大小* loadFactor ,这样就会发生多个线程同时对 Node 数组进行扩容,都在从新计算元素位置以及复制数据,可是最终只有一个线程扩容后的数组会赋给 table,也就是说其余线程的都会丢失,而且各自线程 put 的数据也丢失。数组

《Java并发编程的艺术》一书中是这样说的:HashMap 在并发执行 put 操做时会引发死循环,致使 CPU 利用率接近100%。由于多线程会致使 HashMap 的 Node 链表造成环形数据结构,一旦造成环形数据结构,Node 的 next 节点永远不为空,就会在获取 Node 时产生死循环。安全

死循环并非发生在 put 操做时,而是发生在扩容时。数据结构

如何线程安全的使用HashMap

了解了 HashMap 为何线程不安全,那如今看看如何线程安全的使用 HashMap。这个无非就是如下三种方式:多线程

  • Hashtable
  • ConcurrentHashMap
  • Synchronized Map

ConcurrentHashMap

ConcurrentHashMap (如下简称CHM)是 JUC 包中的一个类,Spring 的源码中有不少使用 CHM 的地方。以前已经翻译过一篇关于 ConcurrentHashMap 的博客,如何在java中使用ConcurrentHashMap,里面介绍了 CHM 在 Java 中的实现,CHM 的一些重要特性和什么状况下应该使用 CHM。须要注意的是,上面博客是基于 Java 7 的,和8有区别,在8中 CHM 摒弃了 Segment(锁段)的概念,而是启用了一种全新的方式实现,利用 CAS 算法,有时间会从新总结一下。并发

相关文章
相关标签/搜索