列表

详情


38. HashMap是线程安全的吗?如果不是该如何解决?

回答思路

得分点 Hashtable、Collections、ConcurrentHashMap 标准回答 HashMap是非线程安全的,在多线程环境下,多个线程同时触发HashMap的改变时,有可能会发生冲突。所以,在多线程环境下不建议使用HashMap。想要使用线程安全的HashMap,一共有三种办法:使用Hashtable、使用Collections将HashMap包装成线程安全的HashMap、使用ConcurrentHashMap,其中第三种方式最为高效,是我们最推荐的方式。 Hashtable HashMap和Hashtable都是典型的Map实现,而Hashtable是线程安全的。虽然这算是一个可选方案,但却是不推荐的方案。因为Hashtable是一个古老的API,从Java 1.0开始就出现了,它的同步方案还不成熟、性能不好,甚至官方都给出了不推荐使用的建议。 Collections Collections类中提供了synchronizedMap()方法,可以将我们传入的Map包装成线程同步的Map。除此以外,Collections还提供了如下三类方法来返回一个不可变的集合,这三类方法的参数是原有的集合对象,返回值是该集合的“只读”版本。通过Collections提供的三类方法,可以生成“只读”Map。 emptyMap():返回一个空的不可变的Map对象。 singletonMap():返回一个只包含指定键值对的不可变的Map对象。 unmodifiableMap() :返回指定Map对象的不可变视图。 ConcurrentHashMap ConcurrentHashMap是线程安全且高效的HashMap,并且在JDK 8中进行了升级,使其在JDK 7的基础上进一步降低了锁的粒度,从而提高了并发的能力。 在JDK 7中ConcurrentHashMap的底层数据结构为“数组+链表”,但是为了降低锁的粒度,JDK7将一个Map拆分为若干子Map,每一个子Map称为一个段。多个段之间是相互独立的,而每个段都包含若干个槽,段中数据发生碰撞时采用链表结构解决。在并发插入数据时,ConcurrentHashMap锁定的是段,而不是整个Map。因为锁的粒度是段,所以这种模式也叫“分段锁”。另外,段在容器初始化的时候就被确定下来了,之后不能更改。而每个段是可以独立扩容的,各个段之间互不影响,所以并不存在并发扩容的问题。 在JDK8中ConcurrentHashMap的底层数据结构为“数组+链表+红黑树”,但是为了进一步降低锁的粒度,JDK8取消了段的设定,而是直接在Map的槽内存储链表或红黑树。并发插入时它锁定的是头节点,相比于段头节点的个数是可以随着扩容而增加的,所以粒度更小。引入红黑树,则是为了在冲突剧烈时,提高查找槽内元素的效率。

上一题