Java 那些年你啃过的ConcurrentHashMap

Java 投稿 29400 0 评论

Java 那些年你啃过的ConcurrentHashMap

最近迷上了并发编程。并发这玩意怎么说呢,就是你平时工作用不到,一用就用在面试上。这不,又卷起了并发容器。

由于篇幅原因,这篇文章不会具体解释那些较为基础的问题,比如为什么散列表数组的长度一定要是2的n次方等。将更多围绕并发这个话题。如有需要,之后会另外讲解。

基础回顾

我们都知道,从JDK1.8起,ConcurrentHashMap底层的数据结构就已经从原来的Segment分段锁变为了数组 + 链表 + 红黑树的形态。

红黑树

红黑树数据结构

红黑树在ConcurrentHashMap里面的体现是一个双向链表:

红黑树插入数据

在插入数据的时候会获取节点的hash值,从而与当前节点p的hash值比较,若插入节点的hash小于当前节点,则dir的值为-1,否则为1:

多线程竞争下的读写操作

由于读操作本身就是天然线程安全的。所以多个线程对同一个桶位同时读并不会有什么问题。

通过源码可以看到,put()方法的核心是putVal():

判断你put进去的这个元素,是处于链表还是处于红黑树上;二就是判断当前插入的key是否与链表或者红黑树上的某个元素一致。如果当前插入key与链表当中所有元素的key都不一致时,那么当前的插入操作就追加到链表的末尾。否则就替换掉key对应的value。

扩容原理

因此需要知道的两个重要字段:

  • MIN_TREEIFY_CAPACITY : 数组初始长度,默认为64

  • TREEIFY_THRESHOLD : 树化阈值,指定桶位链表长度达到8的话,就可能发生树化操作

这里的扩容,指的就是扩大数组的桶个数,从而装下更多的元素。

  • sizeCtl < 0:若为-1则起标记作用,告知其它线程此时正在初始化;若为其它的值表示当前table正在扩容

  • sizeCtl = 0:表示创建table数组时还未进行扩容,没有指定的初始容量

  • sizeCtl > 0:表示当table初始化后下次扩容的触发条件

字段的值可以转化为32位的二进制数值,它的高16位表示扩容标识戳,用来标识扩容的范围,如从长度16扩容到32;低16位表示当前参与扩容的线程数量。

新建一个长度更大的数组,然后将老数组上的元素全部迁移到新的数组去。

提高查询效率。因为链表的查询复杂度是O(n),如果链表过长就会影响查询效率。

映射。因为扩容后元素有的需要迁移到新的位置,有的还是处于和老数组一样的位置,只不过是换了一个数组。

ln和hn代表是否需要进行位置迁移。然后采用尾插法将元素插入。这就是LastRun机制

问题就在于当前是处于并发环境的,而扩容也需要时间。

正在扩容 && 有多个线程正在竞争

没关系,我们依然分情况来讨论。

扩容期间的读操作

答案是可以。但是前提是你这个节点已经迁移结束,如果你是一个正在扩容迁移的节点,那就访问不到。

当一个桶位要进行数据迁移,就会往这个桶位上放置一个ForwardingNode节点。除此之外还需要去标识这个节点是正在迁移还是已经迁移结束了的;

所以当某个线程访问了这个节点,看到它上面存在fwd引用,就说明当前table正在扩容,那么就会根据这个引用上的newtable字段去新数组的对应桶位上找到数据然后返回。

扩容期间的写操作

协助容器进行扩容。

但是假如线程修改的节点正好是一个fwd节点,说明当前节点正处于扩容操作,那么为了节约线程数并且快速完成任务,当前线程就会进行协助扩容。如果有多个线程进行同时写,那么它们都会调用helpTransfer()进行协助扩容。

假设一个容器从16个桶位扩容到32个桶位,有线程A、B两个线程。

所以线程A和线程B共同负责桶位的扩容。

我们在上面有提过,sizeCtl转化为32位后,它的低16位是表示当前参与扩容的线程数量。所以当A线程触发了扩容之后,它就会将sizeCtl低16位的最后一位值+1,表示扩容线程多了一位,当它退出扩容时又会将最后一位的值-1,表示扩容线程少了一位,就这样各个线程共同维护这个字段。

那我要是最后一个退出扩容的线程要怎么维护啊?是的,最后一个线程还有一些别的事情要做。当某一个线程完成任务后去判断sizeCtl的值得时候,发现它的低16位只剩下最后一位是1,再减下去就是0了,那就代表它是最后一个退出扩容的线程。此时它还需要去检查一遍老的table数组,判断是否还有遗漏的slot没有迁移。具体的操作就是去轮询检查是否还留有fwd节点,如果没有的话代表迁移完成,如果有的话还需要继续将它迁移到新的桶位:

总结

如果觉得本文有所收获,可以点赞支持一下呀~

编程笔记 » Java 那些年你啃过的ConcurrentHashMap

赞同 (55) or 分享 (0)
游客 发表我的评论   换个身份
取消评论

表情
(0)个小伙伴在吐槽