concurrentHashMap简单理解

包存在java.util.concurrent

一、JDK1.7版本中的concurrentHashMap

在JDK1.7的版本中,concurrentHashMap的数据结构是由一个segment数组和多个HashEntry组成的。segment数组的意义就是将一个大的table分割成多个小的table来进行加锁,而每一个segment元素存储的是HashEntry数组+链表。

put操作

对于ConcurrentHashMap的数据插入,这里要进行两次Hash去定位数据的存储位置

static class Segment<K,V> extends ReentrantLock implements Serializable {

从上Segment的继承体系可以看出,Segment实现了ReentrantLock,也就带有锁的功能,当执行put操作时,会进行第一次key的hash来定位Segment的位置,如果该Segment还没有初始化,即通过CAS操作进行赋值,然后进行第二次hash操作,找到相应的HashEntry的位置,这里会利用继承过来的锁的特性,在将数据插入指定的HashEntry位置时(链表的尾端),会通过继承ReentrantLock的tryLock()方法尝试去获取锁,如果获取成功就直接插入相应的位置,如果已经有线程获取该Segment的锁,那当前线程会以自旋的方式去继续的调用tryLock()方法去获取锁,超过指定次数就挂起,等待唤醒

get操作
ConcurrentHashMap的get操作跟HashMap类似,只是ConcurrentHashMap第一次需要经过一次hash定位到Segment的位置,然后再hash定位到指定的HashEntry,遍历该HashEntry下的链表进行对比,成功就返回,不成功就返回null

size操作
计算ConcurrentHashMap的元素大小是一个有趣的问题,因为他是并发操作的,就是在你计算size的时候,他还在并发的插入数据,可能会导致你计算出来的size和你实际的size有相差(在你return size的时候,插入了多个数据),要解决这个问题,JDK1.7版本用两种方案

1.第一种方案他会使用不加锁的模式去尝试多次计算ConcurrentHashMap的size,最多三次,比较前后两次计算的结果,结果一致就认为当前没有元素加入,计算的结果是准确的

2.第二种方案是如果第一种方案不符合,他就会给每个Segment加上锁,然后计算ConcurrentHashMap的size返回
在这里就不给出源码解释了,有兴趣的可以边看源码便再次深入理解!

二、JDK1.8做了改进

抛弃了原有的 Segment 分段锁,而采用了CAS + synchronized来保证并发安全性。

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

Node

Node是ConcurrentHashMap存储结构的基本单元,继承于HashMap中的Entry,用于存储数据

static class Node<K,V> implements Map.Entry<K,V> {

TreeNode

TreeNode继承与Node,但是数据结构换成了二叉树结构,它是红黑树的数据的存储结构,用于红黑树中存储数据,当链表的节点数大于8时会转换成红黑树的结构

static final class TreeNode<K,V> extends Node<K,V> {

从JDK1.7版本的ReentrantLock+Segment+HashEntry,到JDK1.8版本中synchronized+CAS+HashEntry+红黑树,总结如下思考:

1、JDK1.8的实现降低锁的粒度,JDK1.7版本锁的粒度是基于Segment的,包含多个HashEntry,而JDK1.8锁的粒度就是HashEntry(首节点)
2、JDK1.8版本的数据结构变得更加简单,使得操作也更加清晰流畅,因为已经使用synchronized来进行同步,所以不需要分段锁的概念,也就不需要Segment这种数据结构了,由于粒度的降低,实现的复杂度也增加了
3、JDK1.8使用红黑树来优化链表,基于长度很长的链表的遍历是一个很漫长的过程,而红黑树的遍历效率是很快的,代替一定阈值的链表,这样形成一个最佳拍档
4、JDK1.8为什么使用内置锁synchronized来代替重入锁ReentrantLock,我觉得有以下几点
1>因为粒度降低了,在相对而言的低粒度加锁方式,synchronized并不比ReentrantLock差,在粗粒度加锁中ReentrantLock可能通过Condition来控制各个低粒度的边界,更加的灵活,而在低粒度中,Condition的优势就没有了
2>JVM的开发团队从来都没有放弃synchronized,而且基于JVM的synchronized优化空间更大,使用内嵌的关键字比使用API更加自然
3>在大量的数据操作下,对于JVM的内存压力,基于API的ReentrantLock会开销更多的内存,虽然不是瓶颈,但是也是一个选择依据

三、ConcurrentHashMap、collections.SynchronizedMap、HashTable之间的区别

首先简单地介绍一下HashMap与ConcurrentHashMap之间的区别:

1、线程安全性方面:ConcurrentHashMap是线程安全的,在并发环境下不需要加额外的同步。

2、同步方面:可以使用collections.SynchronizedMap来包装HashMap作为同步器,其作用于HashTable无太大差别,当每次对Map进行修改操作的时候,都会锁住这个Map对象,而ConcurrentHashMap会基于并发的等级来划分整个Map,来达到线程安全,它只会锁操作的那一段数据而不是整个Map都上锁。

3、可扩展性方面:ConcurrentHashMap具有很好的扩展性,在多线程的环境下性能方面比做了同步的HashMap要好,但是在单线程环境下,HashMap会比ConcurrentHashMap好一点。

ConcurrentHashMap vs Hashtable vs Synchronized Map,虽然三个集合类在多线程并发应用中都是线程安全的,他们有一个重大的差别,就是他们各自实现线程安全的方式。

Collections.synchronizedMap()与ConcurrentHashMap主要区别是:Collections.synchronizedMap()和Hashtable一样,实现上在调用map所有方法时,都对整个map进行同步,而ConcurrentHashMap的实现却更加精细,它对map中的所有桶加了锁。所以,只要要有一个线程访问map,其他线程就无法进入map,而如果一个线程在访问ConcurrentHashMap某个桶时,其他线程,仍然可以对map执行某些操作。这样,ConcurrentHashMap在性能以及安全性方面,明显比Collections.synchronizedMap()更加有优势。同时,同步操作精确控制到桶,所以,即使在遍历map时,其他线程试图对map进行数据修改,也不会抛出ConcurrentModificationException。
ConcurrentHashMap从类的命名就能看出,它必然是个HashMap。而Collections.synchronizedMap()可以接收任意Map实例,实现Map的同步。

哪些方法加锁了:

HashTable---> size()、isEmpty()、 keys()、elements()、contains(Object value)、containsKey(Object key) 、get(Object key) 、                                  put(K key, V value) 、remove(Object key) 、putAll(Map<? extends K, ? extends V> t) 、clear()、clone()、 toString()                            、equals(Object o)、hashCode()、

ConcurrentHashMap--->put()、get()、remove()


参考:https://blog.csdn.net/qq296398300/article/details/79074239 

           https://blog.csdn.net/qq296398300/article/details/79074239 

猜你喜欢

转载自blog.csdn.net/qq_40303781/article/details/86509312