数据结构

HashMap的数据结构是数组+链表。数组中存储的是Entry对象,数组中的每一个Entry元素,又是一个链表的头节点。

线程平安

1.在JDK1.7中,当并发执行扩容操作时会造成环形链和数据失落的状况。

2.在JDK1.8中,在并发执行put操作时会产生数据笼罩的状况。
put操作时会判断是否呈现hash碰撞,假如两个线程A、B都在进行put操作,并且hash函数计算出的插入下标是雷同的,当线程A执行完判断是否呈现hash碰撞后因为工夫片耗尽导致被挂起,而线程B失去工夫片后在该下标处插入了元素,实现了失常的插入,而后线程A取得工夫片,因为之前曾经进行了hash碰撞的判断,所有此时不会再进行判断,而是间接进行插入,这就导致了线程B插入的数据被线程A笼罩了,从而线程不平安。

线程平安的HashMap

线程平安的有HashTable还有Collections.synchronizedMap,两种汇合保障线程平安的计划都是整个汇合加锁。保障线程平安的状况下也升高了效率。
ConcurrentHashMap在保障线程平安的状况下进步运行效率。

ConcurrentHashMap数据结构

ConcurrentHashMap的根本构造是Segment数组,每一个Segment是一个独立的HashMap,当咱们在操作数据时,会对每个独立的Segment加锁,并不影响其余的Segment读取操作。

Get办法:

1.为输出的Key做Hash运算,失去hash值。

2.通过hash值,定位到对应的Segment对象

3.再次通过hash值,定位到Segment当中数组的具体位置。

Put办法:

1.为输出的Key做Hash运算,失去hash值。

2.通过hash值,定位到对应的Segment对象

3.获取可重入锁
4.再次通过hash值,定位到Segment当中数组的具体位置。
5.插入或笼罩HashEntry对象。
6.开释锁。

JDK1.8的实现曾经摒弃了Segment的概念,而是间接用Node数组+链表+红黑树的数据结构来实现,并发管制应用Synchronized和CAS来操作,整个看起来就像是优化过且线程平安的HashMap,尽管在JDK1.8中还能看到Segment的数据结构,然而曾经简化了属性,只是为了兼容旧版本.

JDK1.8版本的ConcurrentHashMap的数据结构曾经靠近HashMap,相对而言,ConcurrentHashMap只是减少了同步的操作来管制并发,从JDK1.7版本的ReentrantLock+Segment+HashEntry,到JDK1.8版本中synchronized+CAS+HashEntry+红黑树,相对而言,总结如下思考

  • JDK1.8的实现升高锁的粒度,JDK1.7版本锁的粒度是基于Segment的,蕴含多个HashEntry,而JDK1.8锁的粒度就是HashEntry(首节点)
  • JDK1.8版本的数据结构变得更加简略,使得操作也更加清晰晦涩,因为曾经应用synchronized来进行同步,所以不须要分段锁的概念,也就不须要Segment这种数据结构了,因为粒度的升高,实现的复杂度也减少了
  • JDK1.8应用红黑树来优化链表,基于长度很长的链表的遍历是一个很漫长的过程,而红黑树的遍历效率是很快的,代替肯定阈值的链表,这样造成一个最佳拍档
  • JDK1.8为什么应用内置锁synchronized来代替重入锁ReentrantLock,我感觉有以下几点
    1.因为粒度升高了,在相对而言的低粒度加锁形式,synchronized并不比ReentrantLock差,在粗粒度加锁中ReentrantLock可能通过Condition来管制各个低粒度的边界,更加的灵便,而在低粒度中,Condition的劣势就没有了
    2.JVM的开发团队素来都没有放弃synchronized,而且基于JVM的synchronized优化空间更大,应用内嵌的关键字比应用API更加天然
    3.在大量的数据操作下,对于JVM的内存压力,基于API的ReentrantLock会开销更多的内存,尽管不是瓶颈,然而也是一个抉择根据。