前言
从9月初,开始写博客开始,因笔者平时也是有工作,陆陆续续经过一周多时间,从学习-》解析-》分析 ,并发包中的第一个并发容器ConcurrentHashMap为止。 过程中确实很花费精力,十分枯燥,也有在某一点上死钻牛角尖的时候,导致一度都想打退堂鼓。 但还是得坚持,慢慢用debug方式,逐行去剖解,分析作者的意图, 每当想明白一个难点时,感觉之前的枯燥呀,烦躁呀,一扫而空,美滋滋。 那么今天就来对ConcurrentHashMap做一次总结。
写作方面:
在开立博客时,笔者写过一篇自己学习的方法论,目前感觉良好:
-
一定得先阅读官方的文档。了解清楚这个玩意有啥功能,有啥特征,然后由面到点。
-
根据咱们平时常用的一些API,逐一展开学习跟踪源码。 学习某个API的时候,也是由面到点,通读文档后,先凭借自己阅码能力,看一遍,把看不明白的代码板块做标记, 其次是脑袋不能空看,得在阅码时,自己去发现问题,心里多一些为什么,为什么。 ok,有些人肯定会说,我问不出来怎么办? 最简单方法,在API源码中,有常量体现的地方,咱们就从对常量理解入手,想为什么是如此设定,由此为线索,慢慢抽丝剥茧。 最后是在看不明白的地方,通过代码debug方式,分析。
-
最后就是得坚持,每次看完后,把自己所思考的精华记下来,这些就是最宝贵的财富。
源码方面:
每一次的总结都意味着重新开始, 肯定没有结束。 ConcurrentHashMap确实是非常的璀璨,笔者也只是阅码了几个常用的方法,其精华部分还需要自我慢慢在消化吸收,同时也为我在后期学习中,指明了一些良好的思考思路。 不多说,笔者把前面几篇学习的文档按照优先级放出来,肯定有理解不对或者思考透的地方,也请大家不吝赐教,也可以通过下图方式,联系我,咱们一起在盘码的路上走起;
JUC1.8-ConcurrentHashMap源码学习-准备 是盘码预前了解篇,其内容包含了 “位运算”、“转二进制”,“CAS原理理解”,“Java内存模型理解”。 对以上内容烂于心的同学可以略过;
思考点:
在JDK源码中,大多采用位运算的方式,因此咱们对于二进制转换和相关位运算【& | ~ << >> >>>】必须得掌握,否者会给阅读带来一定阻碍;
其次针对Java内存模型来说,是一个过程的学习。 如果想学好并发包的源码,那么这个模型一定得烂于心。并发就是针对 原子、有序、可见,三大特性来处理,所以不搞明白底层原理是啥,就想去看表面东西,那不是在瞎扯嘛。
以下4篇按照由上之下即可,由putVal为入口,在到扩容的为结束的学习路程:
JUC1.8-ConcurrentHashMap源码学习-putVal()方法
JUC1.8-ConcurrentHashMap源码学习-容量是2次幂以及如何保证是2次幂
JUC1.8-ConcurrentHashMap源码学习-扩容方法解析transfer()
JUC1.8-ConcurrentHashMap源码学习-为什么每次扩容是原来两倍?
思考点:
putVal方法:
- 不允许key 和 val为null
- 使用CAS算法,volatile,synchronized三个方式确保线程安全,其中synchronized仅用于某节点,锁颗粒更小。
- 新增sizeCtl变量,用于对map整的状态控制;
- init容器,是在第一次put时进行;
- hash冲突时,连用链表储存,放置尾部,长度达到8,转红黑树
- 每当增加数据后,通过addCount 方法,对 size 加一,在对其判断是否需要扩容。 判断条件容器长度 * 0.75
- 新增加ForwardingNode,标识扩容中。 其他put操作时,识别则可以帮忙扩容
- 扩容最大线程为65535,这是低 16 位的最大值限制的。
- 根据当前机器的CPU数,计算区间桶的处理数。 没有达到16, 默认用16一个区间桶;
- 新表在原表的基础上扩容2倍;
- 每个线程在处理当前区间桶后,如原表还有数据,则通过transferIndex减9中的处理数。
- 在put时,如在扩容节点,遇到fwd,那么加入扩容大军
- 转移数据,采用反序对数据进行高低位计算识别。最终拆分成高低位两条链表或者数;
- 转移数据过程中,也得准守链表转数,数转链表规则;
- 当所有数据转移完毕,默认在重新循环一次,一方有遗漏数据;
上面阅读感觉有难度,可以换个思路看下读操作,算是简单方法,看懂增强咱们自信心:
JUC1.8-ConcurrentHashMap源码学习-get()
思考点:
- 读没有加锁的原因:是采用CAS与volatile方式,但一定得注意,volatile Node是保证当前节点的可见,真正保证安全的是Node -> volatile val属性;
- 不同的数据结构,都有属于自己的find辅助查询;
如果我是面试官,我会问哪些问题?
我把阅读过程中,感觉比较隐匿的一些 知识点问题列出,如果我是面试官,问了这些问题,你能回答上来,那么我会真的认为你是真的研究技术:
1、put方法中,在哪些地方使用到同步锁,为什么要使用?
----考察你对源码流程的理解,以及相比JDK1.7中,并发性能做的提升;
2、Map是在何时做的初始化步骤:
3、接着上面问,初始化时怎么保证线程安全,不会重复初始化,初始化的容量怎么定义? 为什么容量一定是2的次幂?
4、聊聊是怎么协助扩容的? 扩容的机制是怎么样的? 扩容量为啥是原来的2倍?
5、读的时候,怎么保证线程安全?
上面几个方法答案,后面我在整理出来,相信这些问题对答如流,那么咱们对这个并发Map面试也算是及格了吧。 大家也可以在我前面的博客里面找找答案,给我留言。
后面笔者将开始对CopyOnWriteArrayList列为下一个攻克点,最后附上笔者联系方式,加好友时,请麻烦备注通过CSDN博客添加,欢迎大家来骚扰。?