ConcurrentHashMap简单的实现思想理解


关于说ConcurrentHashMap的文章很多,本博客也有转载这样的文章,但是总体觉得都是过于偏重源码的说明。没有很明确的结构图来让我们从整体上理解源码的实现过程。毕竟人都是偏向于懒,博客中源码过于太多,理解起来困难,再加上这种源码分析的篇幅很长,因此能真正看完理解的确不多(个人理解)。所以这篇文章里面将不贴出源码,完全通过图来理解ConcurrentHashMap的理解(更直观)。

1. ConcurrentHashMap存储结构

ConcurrentHashMap的存储结构在JDK7和JDK8两个版本是有很大区别的。

在JDK7中采用的是数组+链表结构。数组结构包含外层的Segment和Segment下的第一层HashEntry。链表则是HashEntry三层及以后的采用的。这里的分层是我自己理解上的划分,可能看起来比较抽象,上图:

从图上可以看的出来,第一列是最外层的Segment,在ConcurrentHashMap中,默认的初始长度是16,Segment数组的长度也自然是16。如果按照我之前的理解,第二列应该只会出现单个HashEntry,然后依次的排下去,成为一个链表结构。但是事实并非如此。在第二列中HashEntry也会形成一个数组的结构,然后才会从这个数组结构延伸出去,这样的好处就是让整个链表的长度不会很长,在获取元素的时候行程路径更短,速度更快。

ConcurrentHashMap实现的是分片锁,并不像Hashtable对整个方法加锁。这样的目的也是为了减少锁的行程,提交执行的效率。Segment是继承了ReentrantLock锁的,因此在加锁的阶段是在Segment的时候才会触发,一旦涉及到元素的变动,都会被触发,这一点需要注意,因为这一点将会和JDK8的实现过程有比较。

上面的JDK7的说明大概就是这么多,然后来看看JDK8里面对ConcurrentHashMap的实现,在这个实现的过程对JDK7优化了很多。上图:

在存储结构中使用了数组+链表+红黑树,但是这里需要注意的是,红黑树不一定会出现,这是为什么呢?

首先创建的是一个初始长度的数组,这个和JDK7一样,默认长度都是16,然后用链表结构实现在节点上向后添加元素,这里需要注意的是红黑树不一定会出现,因为只有当后端的链表长度达到8以后才会将原来的链表结构变成红黑树结构。对数据结构有了解的都知道,从一个树上获取一个元素比在一个链表上获取元素的速度要快,具体原理这里不说,有兴趣的可以去了解一下。

在说JDK7的时候说到只要在数组节点上添加元素,就必然会触发锁,这一点会和JDK8形成对比呢。这里就涉及到JDK8的一个改进。就是采用了CAS和原子操作。如果这个节点上添加的是第一个元素的时候,就会出现不会触发锁的问题。具体看下面putValue方法实现流程的说明。

2. ConcurrentHashMap的put方法实现逻辑

不管是ConcurrentHashMap还是HashMap都会涉及到CRUD操作,这里单独的将putValue方法拿出来说一下。看下面的图:

在设置值的开始就是一个for循环(死循环,又称为自旋),在循环的过程中会有一个判断的过程。

首先是判断是不是第一次插入值,如果是的话就要对数组结构进行初始化,当初始化结束后,会再次进入循环,然后就会走到第二个条件判断,在第二判断中检查当前分配的插槽是不是已经有值了,如果没有值就会直接调用原子方法,向插槽中放值,由于到这一步还没有加锁,有可能会被其他线程提供可乘之机,所以会有一个插入失败的处理。如果成功就直接结束循环,如果失败就会进入下一个循环,进入最终的处理逻辑。在这个逻辑中就会对当前插槽位置的元素加锁。最终执行结束会跳出for循环。这个自旋的过程可以减少加锁的次数(至少16次)。性能上也有所提高。

另外要说的是,就以数组长度为16来说,它的最大并发量是16,但是不是很准确,可以作为一个参考,因为在创建ConcurrentHashMap的时候是可以设置这个初始长度的。

这个put的整体实现过程并不是很难理解,希望能让大家对这个整体过程有个了解。如果看了这里,你再去看那些纯源码分析的文章你会发现理解起来就会更容易。

3. 总结

不管在面试还是在实际开发中,到底是选Hashtable、HashMap、ConcurrentHashMap都是有一套说辞的,下面大概比较一下。

  • Hashtable是线程安全的,在所有的方法上都加上了synchronized锁,也就是并发量是1,每个瞬态只能有一个线程访问,在保证线程安全的同时,也是将性能大大的降低了。
  • HashMap是没有锁的概念,并发量也是没有限制的,所以效率很高,但是同时带来的问题也很明显,就是存在数据安全问题。
  • ConcurrentHashMap是对HashMap的改进,也融入了Hashtable的思想,既能保证数据的安全,也能保证能并发操作,只不过这个并发操作相对于HashMap的效率要低一点,但是基本够用(也可以说完全够用)。

其实到现在在项目应用中,具体的选择也就只有HashMap和ConcurrentHashMap两种了,如果单线程操作,当然选择HashMap,没有锁的竞争问题,效率高。如果涉及到多线程的数据安全问题,那就选择ConcurrentHashMap,它可以兼顾数据安全问题和并发问题,同时也将锁的竞争带来的效率问题在一定程度上降低。至于Hashtable基本没有使用(除非一些很老的项目),因为在HashMap和ConcurrentHashMap面前它好像没有什么竞争优势。


文章作者: 程序猿洞晓
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 程序猿洞晓 !
评论
 上一篇
下一篇 
MySQL数据库系列(六):MySQL之索引数据结构分析 MySQL数据库系列(六):MySQL之索引数据结构分析
数据库查询是数据库的最主要功能之一。我们都希望查询数据的速度能尽可能的快,因此数据库系统的设计者会从查询算法的角度进行优化。最基本的查询算法当然是顺序查找(linear search),这种复杂度为O(n)的算法在数据量很大时显然是糟糕的,好在计算机科学的发展提供了很多更优秀的查找算法,例如二分查找(binary search)……
2018-10-16
  目录