HashMap,HashTable以及ConcurrentHashMap浅谈

题外话:

HashMap等为什么用16作为初始容量?经验值。

Hashmap中的链表大小超过八个时会自动转化为红黑树,当删除小于六时重新变为链表,为啥呢?

因为根据泊松分布,在负载因子为0.75的情况下,单个hash槽内元素个数为8的概率小于百万分之一,所以将7作为一个分水岭。

 

1.线程安全的处理:

HashMap:线程不安全。

Hashtable,Collections.synchronizedMap(Map), ConcurrentHashMap

 

2.线程安全的实现原理:

 

Collections.synchronizedMap:

方法源码:

 

其中SynchronizedMap类如下,

 

HashMap,HashTable以及ConcurrentHashMap浅谈_第1张图片

在SynchronizedMap内部维护了一个普通对象Map以及排斥锁mutex。对于Collections.synchronizedMap(),内部调用的是第一个构造函数,会将对象的排斥所赋值为this,即返回的Map对象,对于该对象的操作都会进行加锁处理。

 

 

 

HashMap,HashTable以及ConcurrentHashMap浅谈_第2张图片

Hashtable:

简单粗暴,对于数据操作的方法直接上锁,即加上synchronized。这也导致了其处理效率比较低。这边还要说一下Hashtable是不支持key或value为null的,HashMap却可以。我们看一下源码:

 

 

HashMap,HashTable以及ConcurrentHashMap浅谈_第3张图片

对于value为空的,直接抛异常,对于key为null的,则在.hashCode()时时抛异常,至于为什么要这么设计,可以说是hashtable是比较古老的设计,当时的设计师觉得空值的存在并没有必要。这边简单提一下,Hashtable和hashmap一样时fail-fast的,具体的我们下面会谈到,我也看到过有些说hashtable是fail-safe的,这边欢迎探讨。

 

 

ConcurrentHashMap:

首先,这个数据结构的并发效率比较高,这与其内部的结构是分不开的。具体我们从JDK1.7开始谈起,1.7中的ConcurrentHashMap的结构如图所示:

 

HashMap,HashTable以及ConcurrentHashMap浅谈_第4张图片HashMap,HashTable以及ConcurrentHashMap浅谈_第5张图片

 

这里采用了分段锁的设计,所以相对HashTable更加高效,但是这种设计依旧有个问题,那就是其基本上还是数组加链表的方式,那么我们去查询的时候还是要遍历链表,这样效率并不高。所以在jdk1.8中它变了。

具体说来,jdk1.8中的hashmap采用了CAS+synchronized来保证并发安全性。其中CAS就是compare and swap,即比较然后替换。

CAS:可以理解为一种轻量级锁,即在替换前先比较一下,判断数据是否和一开始读到的一致,如果一致就替换,不一致就重新读取。不过这里还会涉及到一个ABA问题,就是数据从A改到B,又改到A,刚好你CAS最后比较的时候是A,看上去比较成功了,但数据其实已经被改了,这个时候我们就需要引入一些变化量来保证其没有被改,比如hashmap中的modcount,也就是改一次加1,那么我们CAS比较的时候除了比较数值,也比较这个modcount是不是和读取到的一致,这样基本上就保证了正确性。

此外,在JDK1.8中,针对 synchronized 获取锁的方式,JVM 使用了锁升级的优化方式,就是先使用偏向锁优先同一线程然后再次获取锁,如果失败,就升级为CAS 轻量级锁,如果失败就会短暂自旋,防止线程被系统挂起。最后如果以上都失败就升级为重量级锁。同时1.8中的数据结构也采用了红黑树,这点和hashmap类似。链表-》红黑树。

 

对于fail-fast和fail-safe,网上有很多的博客,但基本都差不多,没有自己的灵魂,这边我就针对自己学习过程中的困惑进行一些讨论。

 

fail-fast:

 

fail-safe:

未完待续。。。

 

 

你可能感兴趣的:(JAVA基础,java多线程,HashMap,并发,fail-fast,Hashtable)